From 94ffa4865e736746bc9f87edd4393539898bf63b Mon Sep 17 00:00:00 2001 From: Brian Meeker Date: Mon, 22 Feb 2016 11:19:26 -0500 Subject: [PATCH 001/147] Initial commit. --- .gitignore | 37 ++++ README.md | 31 +++- credit-offers/app.js | 76 +++++++++ credit-offers/bin/www | 90 ++++++++++ credit-offers/config.js | 25 +++ credit-offers/creditOffersClient.js | 105 ++++++++++++ credit-offers/package.json | 26 +++ credit-offers/public/stylesheets/style.css | 46 +++++ credit-offers/routes/index.js | 24 +++ credit-offers/routes/offers.js | 43 +++++ credit-offers/views/error.jade | 20 +++ .../views/includes/customer-form.jade | 120 +++++++++++++ credit-offers/views/index.jade | 38 +++++ credit-offers/views/layout.jade | 29 ++++ credit-offers/views/offers.jade | 39 +++++ credit-offers_mock_api/app.js | 57 +++++++ credit-offers_mock_api/bin/www | 90 ++++++++++ credit-offers_mock_api/package.json | 16 ++ .../public/images/chicken_thumbnail.jpg | Bin 0 -> 18121 bytes .../public/images/dog_nose_thumbnail.jpg | Bin 0 -> 15089 bytes .../public/images/hammock_thumbnail.jpg | Bin 0 -> 16420 bytes .../public/images/steps_thumbnail.jpg | Bin 0 -> 15551 bytes .../public/images/water_huts_thumbnail.jpg | Bin 0 -> 8060 bytes credit-offers_mock_api/routes/index.js | 158 ++++++++++++++++++ credit-offers_mock_api/routes/oauth.js | 55 ++++++ credit-offers_mock_api/views/authorize.jade | 9 + credit-offers_mock_api/views/error.jade | 19 +++ credit-offers_mock_api/views/layout.jade | 21 +++ 28 files changed, 1173 insertions(+), 1 deletion(-) create mode 100644 .gitignore create mode 100644 credit-offers/app.js create mode 100644 credit-offers/bin/www create mode 100644 credit-offers/config.js create mode 100644 credit-offers/creditOffersClient.js create mode 100644 credit-offers/package.json create mode 100644 credit-offers/public/stylesheets/style.css create mode 100644 credit-offers/routes/index.js create mode 100644 credit-offers/routes/offers.js create mode 100644 credit-offers/views/error.jade create mode 100644 credit-offers/views/includes/customer-form.jade create mode 100644 credit-offers/views/index.jade create mode 100644 credit-offers/views/layout.jade create mode 100644 credit-offers/views/offers.jade create mode 100644 credit-offers_mock_api/app.js create mode 100644 credit-offers_mock_api/bin/www create mode 100644 credit-offers_mock_api/package.json create mode 100644 credit-offers_mock_api/public/images/chicken_thumbnail.jpg create mode 100644 credit-offers_mock_api/public/images/dog_nose_thumbnail.jpg create mode 100644 credit-offers_mock_api/public/images/hammock_thumbnail.jpg create mode 100644 credit-offers_mock_api/public/images/steps_thumbnail.jpg create mode 100644 credit-offers_mock_api/public/images/water_huts_thumbnail.jpg create mode 100644 credit-offers_mock_api/routes/index.js create mode 100644 credit-offers_mock_api/routes/oauth.js create mode 100644 credit-offers_mock_api/views/authorize.jade create mode 100644 credit-offers_mock_api/views/error.jade create mode 100644 credit-offers_mock_api/views/layout.jade diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1ffcadd --- /dev/null +++ b/.gitignore @@ -0,0 +1,37 @@ +# Thumbs +Thumbs.db + +# Logs +logs +*.log +npm-debug.log* + +# Runtime data +pids +*.pid +*.seed + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules +jspm_packages + +# Optional npm cache directory +.npm + +# Optional REPL history +.node_repl_history diff --git a/README.md b/README.md index 8e331e1..db7e4c5 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,32 @@ # Credit Offers API Reference App -Stuff here about how to build and use this +*Summary of Credit Offers goes here.* + +This reference app illustrates the use of the Credit Offers API to collect customer information and retrieve a list of targeted product offers for display. + +## Getting Started + +### config.js +You can configure your clientID and clientSecret in credit-offers/config.js. In addition, if you change the default port for the mock API, you also need to update this file. + +### Start the mock API +From the project root: +`cd credit-offers_mock_api` +`npm install` +`npm start` + +### Start the app +From the project root: +`cd credit-offers` +`npm install` +`npm start` + +### Try it out + +Navigate to http://localhost:3000. You will see a simple page with a button to launch a customer info modal. Enter some fake customer data (at least the minimum required fields) and submit the form. + +This will submit a request to the Credit Offers endpoint and redirect the user to a page displaying the offers. + +### Viewing more details + +To get a deeper look at the messages being passed, start the app with the following command `DEBUG=credit-offers:* NODE_DEBUG=request npm start`. This will activate detailed debug logging to the console, showing the details of the request to the API and the response received. diff --git a/credit-offers/app.js b/credit-offers/app.js new file mode 100644 index 0000000..b241681 --- /dev/null +++ b/credit-offers/app.js @@ -0,0 +1,76 @@ +/* +Copyright 2016 Capital One Services, LLC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and limitations under the License. +*/ + +var express = require('express'); +var path = require('path'); +var favicon = require('serve-favicon'); +var logger = require('morgan'); +var cookieParser = require('cookie-parser'); +var bodyParser = require('body-parser'); + +var index = require('./routes/index'); +var offers = require('./routes/offers') + +var app = express(); +var config = require('./config'); + +// view engine setup +app.set('views', path.join(__dirname, 'views')); +app.set('view engine', 'jade'); + +// uncomment after placing your favicon in /public +//app.use(favicon(path.join(__dirname, 'public', 'favicon.ico'))); +app.use(logger('dev')); +app.use(bodyParser.json()); +app.use(bodyParser.urlencoded({ extended: false })); +app.use(cookieParser()); +app.use(express.static(path.join(__dirname, 'public'))); + +app.use('/', index); +app.use('/offers', offers(config.creditOffers)); + +// catch 404 and forward to error handler +app.use(function(req, res, next) { + var err = new Error('Not Found'); + err.status = 404; + next(err); +}); + +// error handlers + +// development error handler +// will print stacktrace +if (app.get('env') === 'development') { + app.use(function(err, req, res, next) { + res.status(err.status || 500); + res.render('error', { + message: err.message, + error: err + }); + }); +} + +// production error handler +// no stacktraces leaked to user +app.use(function(err, req, res, next) { + res.status(err.status || 500); + res.render('error', { + message: err.message, + error: {} + }); +}); + + +module.exports = app; diff --git a/credit-offers/bin/www b/credit-offers/bin/www new file mode 100644 index 0000000..ea78b12 --- /dev/null +++ b/credit-offers/bin/www @@ -0,0 +1,90 @@ +#!/usr/bin/env node + +/** + * Module dependencies. + */ + +var app = require('../app'); +var debug = require('debug')('credit-offers:server'); +var http = require('http'); + +/** + * Get port from environment and store in Express. + */ + +var port = normalizePort(process.env.PORT || '3000'); +app.set('port', port); + +/** + * Create HTTP server. + */ + +var server = http.createServer(app); + +/** + * Listen on provided port, on all network interfaces. + */ + +server.listen(port); +server.on('error', onError); +server.on('listening', onListening); + +/** + * Normalize a port into a number, string, or false. + */ + +function normalizePort(val) { + var port = parseInt(val, 10); + + if (isNaN(port)) { + // named pipe + return val; + } + + if (port >= 0) { + // port number + return port; + } + + return false; +} + +/** + * Event listener for HTTP server "error" event. + */ + +function onError(error) { + if (error.syscall !== 'listen') { + throw error; + } + + var bind = typeof port === 'string' + ? 'Pipe ' + port + : 'Port ' + port; + + // handle specific listen errors with friendly messages + switch (error.code) { + case 'EACCES': + console.error(bind + ' requires elevated privileges'); + process.exit(1); + break; + case 'EADDRINUSE': + console.error(bind + ' is already in use'); + process.exit(1); + break; + default: + throw error; + } +} + +/** + * Event listener for HTTP server "listening" event. + */ + +function onListening() { + var addr = server.address(); + var bind = typeof addr === 'string' + ? 'pipe ' + addr + : 'port ' + addr.port; + debug('Listening on ' + bind); +} diff --git a/credit-offers/config.js b/credit-offers/config.js new file mode 100644 index 0000000..effda10 --- /dev/null +++ b/credit-offers/config.js @@ -0,0 +1,25 @@ +/* +Copyright 2016 Capital One Services, LLC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and limitations under the License. +*/ + +module.exports = { + // Settings for connecting to the Credit Offers API + creditOffers: { + // Change this to the URL of the environment you are connecting to + url: 'http://localhost:3002', + apiVersion: 1, + clientID: 'COF_CLIENT_ID', + clientSecret: 'COF_CLIENT_SECRET' + } +}; diff --git a/credit-offers/creditOffersClient.js b/credit-offers/creditOffersClient.js new file mode 100644 index 0000000..7a08073 --- /dev/null +++ b/credit-offers/creditOffersClient.js @@ -0,0 +1,105 @@ +/* +Copyright 2016 Capital One Services, LLC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and limitations under the License. +*/ + +var request = require('request'); +var qs = require('querystring'); +var _ = require('lodash'); +var format = require('util').format; +var debug = require('debug')('credit-offers:api-client'); + +// Default to a secure call to the API endpoint +var defaultOptions = { + // TODO: update this value with actual/staging api host + url: 'https://api.capitalone.com', + apiVersion: 1, + clientID: null, + clientSecret: null +}; + +/** + * The API client class + * @param options {object} Client options (host url, API version) + */ +function CreditOffersClient(options) { + if(!this instanceof CreditOffersClient) { + return new CreditOffersClient(options); + } + + // Store the supplied options, using default values if not specified + this.options = _.defaults({}, options, defaultOptions); +} +module.exports = CreditOffersClient; + +/** + * Perform a request to retrieve credit offers for a customer + * @param customerInfo {object} Represents the customer info to pass to the API + */ +CreditOffersClient.prototype.getTargetedProductsOffer = function getTargetedProductsOffer(customerInfo, callback) { + var reqOptions = { + baseUrl: this.options.url, + url: '/credit-cards/targeted-products-offer', + method: 'GET', + qs: customerInfo, + headers: { + 'Accept': 'application/json; v=' + this.options.apiVersion + } + }; + debug('Sending request for targeted product offers', reqOptions); + this._sendRequest(reqOptions, callback); +}; + +/** + * A private function to send a request to the API and parse the response, handling errors as needed + */ +CreditOffersClient.prototype._sendRequest = function _sendRequest(reqOptions, callback) { + request(reqOptions, function(err, response, body){ + if(err) { return callback(err); } + if(response.statusCode === 400) { + return processResponseErrors(body, callback); + } else if(response.statusCode === 200) { + debug('Received response', body); + parseResponse(body, callback); + } else { + console.error('Received unexpected status code: ' + response.statusCode); + return callback(new Error('')); + } + }); + + function parseResponse(responseBody, callback) { + if(!responseBody){ + return callback(null, null); + } + + try{ + var responseObject = JSON.parse(responseBody); + return callback(null, responseObject); + } catch(error) { + return callback(error); + } + } + + function processResponseErrors(responseBody, callback) { + parseResponse(responseBody, function(err, data){ + if(err) { return callback(err); } + + var errorCode = data.code || ''; + var errorDescription = data.description || ''; + var documentationUrl = data.documentationUrl || ''; + var message = format('Received an error from the API: code=%s | description=%s | documentation=%s', errorCode, errorDescription, documentationUrl); + console.error(message); + callback(new Error(message)); + }); + } +} diff --git a/credit-offers/package.json b/credit-offers/package.json new file mode 100644 index 0000000..a0c31e4 --- /dev/null +++ b/credit-offers/package.json @@ -0,0 +1,26 @@ +{ + "name": "credit-offers", + "version": "0.0.1", + "private": true, + "licenses": [ + { + "type": "Apache-2.0", + "url": "http://www.apache.org/licenses/LICENSE-2.0" + } + ], + "scripts": { + "start": "node ./bin/www" + }, + "dependencies": { + "body-parser": "~1.13.2", + "cookie-parser": "~1.3.5", + "debug": "~2.2.0", + "ejs": "~2.3.3", + "express": "~4.13.1", + "jade": "^1.11.0", + "lodash": "^4.1.0", + "morgan": "~1.6.1", + "request": "^2.69.0", + "serve-favicon": "~2.3.0" + } +} diff --git a/credit-offers/public/stylesheets/style.css b/credit-offers/public/stylesheets/style.css new file mode 100644 index 0000000..2aa72ff --- /dev/null +++ b/credit-offers/public/stylesheets/style.css @@ -0,0 +1,46 @@ +/* +Copyright 2016 Capital One Services, LLC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and limitations under the License. +*/ + +body { + padding-bottom: 20px; + padding-top: 20px; + font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; } + +.form-group.required > label:after { + content: " *"; + color: red; } + +.form-note { + margin-top: 25px; + margin-bottom: 10px; + color: #888; + padding: 5px 0; + border-bottom: 1px solid #ccc; +} + +.product-offers { + margin-top: 20px; } + .product-offers .product-offer { + width: 500px; + margin-left: auto; + margin-right: auto; + padding: 20px; + border: 1px solid #ccc; + border-radius: 5px; + box-shadow: #ddd 0 2px; } + .product-offers .product-offer .product-image { + width: 100px; } + .product-offers .product-offer .primary-benefit { + font-style: italic; } diff --git a/credit-offers/routes/index.js b/credit-offers/routes/index.js new file mode 100644 index 0000000..88f8aaa --- /dev/null +++ b/credit-offers/routes/index.js @@ -0,0 +1,24 @@ +/* +Copyright 2016 Capital One Services, LLC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and limitations under the License. +*/ + +var express = require('express'); +var router = express.Router(); + +/* GET home page. */ +router.get('/', function(req, res, next) { + res.render('index', { title: 'Credit Offers Sample' }); +}); + +module.exports = router; diff --git a/credit-offers/routes/offers.js b/credit-offers/routes/offers.js new file mode 100644 index 0000000..a4f6120 --- /dev/null +++ b/credit-offers/routes/offers.js @@ -0,0 +1,43 @@ +/* +Copyright 2016 Capital One Services, LLC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and limitations under the License. +*/ + +/* Defines routes related to finding and displaying credit offers */ + +var express = require('express'); +var _ = require('lodash'); +var CreditOffersClient = require('../creditOffersClient'); + +module.exports = function(options){ + var router = express.Router(); + var client = new CreditOffersClient(options); + + // POST customer info to check for offers + router.post('/', function(req, res, next){ + var customerInfo = req.body; + client.getTargetedProductsOffer(customerInfo, function(err, response){ + if(err) { return next(err); } + + var viewModel = { + title: 'Credit Offers', + isPrequalified: response.isPrequalified, + products: response.products && _.sortBy(response.products, 'priority') + }; + + res.render('offers', viewModel); + }); + }); + + return router; +}; diff --git a/credit-offers/views/error.jade b/credit-offers/views/error.jade new file mode 100644 index 0000000..f46cb80 --- /dev/null +++ b/credit-offers/views/error.jade @@ -0,0 +1,20 @@ +//- Copyright 2016 Capital One Services, LLC +//- +//- Licensed under the Apache License, Version 2.0 (the "License"); +//- you may not use this file except in compliance with the License. +//- You may obtain a copy of the License at +//- +//- http://www.apache.org/licenses/LICENSE-2.0 +//- +//- Unless required by applicable law or agreed to in writing, software +//- distributed under the License is distributed on an "AS IS" BASIS, +//- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//- See the License for the specific language governing permissions and limitations under the License. + +extends ./layout.jade + +block content + h1=message + h2=error.status + pre + error.stack diff --git a/credit-offers/views/includes/customer-form.jade b/credit-offers/views/includes/customer-form.jade new file mode 100644 index 0000000..ccdc718 --- /dev/null +++ b/credit-offers/views/includes/customer-form.jade @@ -0,0 +1,120 @@ +//- Copyright 2016 Capital One Services, LLC +//- +//- Licensed under the Apache License, Version 2.0 (the "License"); +//- you may not use this file except in compliance with the License. +//- You may obtain a copy of the License at +//- +//- http://www.apache.org/licenses/LICENSE-2.0 +//- +//- Unless required by applicable law or agreed to in writing, software +//- distributed under the License is distributed on an "AS IS" BASIS, +//- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//- See the License for the specific language governing permissions and limitations under the License. + +//- A re-usable bootstrap text input +mixin textinput(name, label, required) + div.form-group(class={required: required}) + label(for=name)= label + input.form-control(type='text', name=name, placeholder=attributes.placeholder, required=required || false) + +mixin select(name, label, required) + div.form-group(class={required: required}) + label(for=name)= label + select.form-control(name=name) + block + +mixin stateOptions() + option(value='AL') AL + option(value='AK') AK + option(value='AS') AS + option(value='AZ') AZ + option(value='AR') AR + option(value='CA') CA + option(value='CO') CO + option(value='CT') CT + option(value='DE') DE + option(value='DC') DC + option(value='FM') FM + option(value='FL') FL + option(value='GA') GA + option(value='GU') GU + option(value='HI') HI + option(value='ID') ID + option(value='IL') IL + option(value='IN') IN + option(value='IA') IA + option(value='KS') KS + option(value='KY') KY + option(value='LA') LA + option(value='ME') ME + option(value='MH') MH + option(value='MD') MD + option(value='MA') MA + option(value='MI') MI + option(value='MN') MN + option(value='MS') MS + option(value='MO') MO + option(value='MT') MT + option(value='NE') NE + option(value='NV') NV + option(value='NH') NH + option(value='NJ') NJ + option(value='NM') NM + option(value='NY') NY + option(value='NC') NC + option(value='ND') ND + option(value='MP') MP + option(value='OH') OH + option(value='OK') OK + option(value='OR') OR + option(value='PW') PW + option(value='PA') PA + option(value='PR') PR + option(value='RI') RI + option(value='SC') SC + option(value='SD') SD + option(value='TN') TN + option(value='TX') TX + option(value='UT') UT + option(value='VT') VT + option(value='VI') VI + option(value='VA') VA + option(value='WA') WA + option(value='WV') WV + option(value='WI') WI + option(value='WY') WY + option(value='DC') DC + option(value='AA') AA + option(value='AE') AE + option(value='AP') AP + ++textinput('firstName', 'First Name', true) ++textinput('middleName', 'Middle Name') ++textinput('lastName', 'Last Name', true) ++textinput('nameSuffix', 'Suffix')(placeholder='E.g. Jr, Sr') ++textinput('addressLine1', 'Address Line 1', true) ++textinput('addressLine2', 'Address Line 2') ++textinput('city', 'City', true) ++select('stateCode', 'State', true) + +stateOptions() ++textinput('postalCode', 'Zip Code', true) ++textinput('taxId', 'Last Four Digits of SSN', true) ++textinput('dateOfBirth', 'Birth Date', true)(placeholder='YYYY-MM-DD') + +div.form-note The below information is optional but can be used to fine tune the offers returned ++textinput('emailAddress', 'Email Address') ++select('primaryBenefit', 'Most Desired Credit Card Benefit') + option(value='NotSure') Not sure + option(value='LowInterest') Low Interest Rate + option(value='TravelRewards') Travel Rewards + option(value='CashBack') Cash Back Rewards ++select('selfAssessedCreditRating', 'Credit Rating') + option(value='Excellent') Excellent + option(value='Average') Average + option(value='Rebuilding') Rebuilding ++textinput('annualIncome', 'Annual Income (dollars)') ++select('bankAccountSummary', 'Type of Bank Accounts') + option(value='CheckingAndSavings') Checking & Savings + option(value='CheckingOnly') Checking Only + option(value='SavingsOnly') Savings Only + option(value='Neither', selected) Neither diff --git a/credit-offers/views/index.jade b/credit-offers/views/index.jade new file mode 100644 index 0000000..e1aa81e --- /dev/null +++ b/credit-offers/views/index.jade @@ -0,0 +1,38 @@ +//- Copyright 2016 Capital One Services, LLC +//- +//- Licensed under the Apache License, Version 2.0 (the "License"); +//- you may not use this file except in compliance with the License. +//- You may obtain a copy of the License at +//- +//- http://www.apache.org/licenses/LICENSE-2.0 +//- +//- Unless required by applicable law or agreed to in writing, software +//- distributed under the License is distributed on an "AS IS" BASIS, +//- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//- See the License for the specific language governing permissions and limitations under the License. + +extends ./layout.jade + +block content + div.jumbotron + h1 You Deserve a Better Credit Card! + p + button.btn.btn-lg.btn-success(role='button', data-toggle='modal', data-target='#customer-info') See Your Offers + +block modals + div#customer-info.modal.fade(tabindex='-1', role='dialog') + div.modal-dialog + div.modal-content + form(action='/offers', method='POST') + div.modal-header + button.close(type='button', data-dismiss='modal', aria-label='close') + span(aria-hidden='true') + × + h4.modal-title Tell us a little about yourself + div.modal-body + include ./includes/customer-form.jade + div.modal-footer + button.btn.btn-default(type='button', data-dismiss='modal') + | Close + button.btn.btn-primary(type='submit') + | See Offers diff --git a/credit-offers/views/layout.jade b/credit-offers/views/layout.jade new file mode 100644 index 0000000..42c1d0e --- /dev/null +++ b/credit-offers/views/layout.jade @@ -0,0 +1,29 @@ +//- Copyright 2016 Capital One Services, LLC +//- +//- Licensed under the Apache License, Version 2.0 (the "License"); +//- you may not use this file except in compliance with the License. +//- You may obtain a copy of the License at +//- +//- http://www.apache.org/licenses/LICENSE-2.0 +//- +//- Unless required by applicable law or agreed to in writing, software +//- distributed under the License is distributed on an "AS IS" BASIS, +//- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//- See the License for the specific language governing permissions and limitations under the License. + +doctype html(lang='en') +head + meta(charset='utf-8') + meta(http-equiv='x-ua-compatible', content='ie=edge') + title=title + meta(name='description', content='') + meta(name='viewport', content='width=device-width, initial-scale=1') + link(rel='stylesheet', href='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css', integrity='sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7', crossorigin='anonymous') + link(rel='stylesheet', href='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap-theme.min.css', integrity='sha384-fLW2N01lMqjakBkx3l/M9EahuwpSfeNvV63J5ezn3uZzapT0u7EYsXMjQV+0En5r', crossorigin='anonymous') + link(rel='stylesheet', href='/stylesheets/style.css') + script(src='https://code.jquery.com/jquery-2.2.0.min.js') + script(src='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js', integrity='sha384-0mSbJDEHialfmuBBQP6A4Qrprq5OVfW37PRR3j5ELqxss1yVqOtnepnHVP9aJ7xS', crossorigin='anonymous') + body + div.container + block content + block modals diff --git a/credit-offers/views/offers.jade b/credit-offers/views/offers.jade new file mode 100644 index 0000000..60a9b3e --- /dev/null +++ b/credit-offers/views/offers.jade @@ -0,0 +1,39 @@ +//- Copyright 2016 Capital One Services, LLC +//- +//- Licensed under the Apache License, Version 2.0 (the "License"); +//- you may not use this file except in compliance with the License. +//- You may obtain a copy of the License at +//- +//- http://www.apache.org/licenses/LICENSE-2.0 +//- +//- Unless required by applicable law or agreed to in writing, software +//- distributed under the License is distributed on an "AS IS" BASIS, +//- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//- See the License for the specific language governing permissions and limitations under the License. + +extends ./layout.jade + +block content + p.offer-summary + if error + span.error= error + else if isPrequalified + h1 Good news! You already qualify! + else if products && products.length + h1 Check out these great offers! + else + h1 We weren't able to find any offers + + if products + div.product-offers + each product in products + div.product-offer.media + div.media-left + img.product-image( + src = product.image.url.href + alt = product.image.alternateText + ) + div.media-body + h4.media-heading= product.name + if product.terms && product.terms.primaryBenefit + div.primary-benefit= product.terms.primaryBenefit diff --git a/credit-offers_mock_api/app.js b/credit-offers_mock_api/app.js new file mode 100644 index 0000000..e4f670c --- /dev/null +++ b/credit-offers_mock_api/app.js @@ -0,0 +1,57 @@ +var express = require('express'); +var path = require('path'); +var logger = require('morgan'); +var cookieParser = require('cookie-parser'); +var bodyParser = require('body-parser'); + +var routes = require('./routes/index'); +var oauth = require('./routes/oauth'); + +var app = express(); + +// view engine setup +app.set('views', path.join(__dirname, 'views')); +app.set('view engine', 'jade'); + +app.use(logger('dev')); +app.use(bodyParser.json()); +app.use(bodyParser.urlencoded({ extended: false })); +app.use(cookieParser()); +app.use(express.static(path.join(__dirname, 'public'))); + +app.use('/', routes); +app.use('/oauth/', oauth); + +// catch 404 and forward to error handler +app.use(function(req, res, next) { + var err = new Error('Not Found'); + err.status = 404; + next(err); +}); + +// error handlers + +// development error handler +// will print stacktrace +if (app.get('env') === 'development') { + app.use(function(err, req, res, next) { + res.status(err.status || 500); + res.render('error', { + message: err.message, + error: err + }); + }); +} + +// production error handler +// no stacktraces leaked to user +app.use(function(err, req, res, next) { + res.status(err.status || 500); + res.render('error', { + message: err.message, + error: {} + }); +}); + + +module.exports = app; diff --git a/credit-offers_mock_api/bin/www b/credit-offers_mock_api/bin/www new file mode 100644 index 0000000..c278958 --- /dev/null +++ b/credit-offers_mock_api/bin/www @@ -0,0 +1,90 @@ +#!/usr/bin/env node + +/** + * Module dependencies. + */ + +var app = require('../app'); +var debug = require('debug')('credit-offers_mock_api:server'); +var http = require('http'); + +/** + * Get port from environment and store in Express. + */ + +var port = normalizePort(process.env.PORT || '3002'); +app.set('port', port); + +/** + * Create HTTP server. + */ + +var server = http.createServer(app); + +/** + * Listen on provided port, on all network interfaces. + */ + +server.listen(port); +server.on('error', onError); +server.on('listening', onListening); + +/** + * Normalize a port into a number, string, or false. + */ + +function normalizePort(val) { + var port = parseInt(val, 10); + + if (isNaN(port)) { + // named pipe + return val; + } + + if (port >= 0) { + // port number + return port; + } + + return false; +} + +/** + * Event listener for HTTP server "error" event. + */ + +function onError(error) { + if (error.syscall !== 'listen') { + throw error; + } + + var bind = typeof port === 'string' + ? 'Pipe ' + port + : 'Port ' + port; + + // handle specific listen errors with friendly messages + switch (error.code) { + case 'EACCES': + console.error(bind + ' requires elevated privileges'); + process.exit(1); + break; + case 'EADDRINUSE': + console.error(bind + ' is already in use'); + process.exit(1); + break; + default: + throw error; + } +} + +/** + * Event listener for HTTP server "listening" event. + */ + +function onListening() { + var addr = server.address(); + var bind = typeof addr === 'string' + ? 'pipe ' + addr + : 'port ' + addr.port; + debug('Listening on ' + bind); +} diff --git a/credit-offers_mock_api/package.json b/credit-offers_mock_api/package.json new file mode 100644 index 0000000..75eb376 --- /dev/null +++ b/credit-offers_mock_api/package.json @@ -0,0 +1,16 @@ +{ + "name": "credit_offers_mock_api", + "version": "0.0.0", + "private": true, + "scripts": { + "start": "node ./bin/www" + }, + "dependencies": { + "body-parser": "~1.13.2", + "cookie-parser": "~1.3.5", + "debug": "~2.2.0", + "express": "~4.13.1", + "morgan": "~1.6.1", + "request": "^2.69.0" + } +} diff --git a/credit-offers_mock_api/public/images/chicken_thumbnail.jpg b/credit-offers_mock_api/public/images/chicken_thumbnail.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0ae97c7b99c8ea74cf1b695ed29fd87fb6932a72 GIT binary patch literal 18121 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

!|t?b4@PW5%e36 zKCz;>A3RtKPlw~<%f@Id?y(MQ=lGs`hb>B@&$Z4jinINY{|p_AD25)1hQFT*o&Obx zkZE<}U{SQ0>Zv?&GhivQUu>qdvm*VDKuTG`wU_Zk_i{M!+6YXib6vMW9yyg$1Y~(q z7;h`?95=qDcq`P?io-UV(&^G$#Y)KZ9i#NGVG<589phO7n%(LE+bq-^7I; zRZOiK)&S$Ye6utu?2B>w+0;i?)yK8dZdv&gOD{<0FXb(&ZniFc(CQT~;CJz|nNkta zlT54@W^(C#_BC{xpt$}HGrF!U7OW+(o$l!~Gs&a66CAXGD2@)}P?k4!r;Ff{DA|X+ z=u5p#v`M)iqjia&(D&$M?ucSH8Y;~3uyoVwY#rt%76WSGjxi4(yjh0-46|_5TvGe` zB9l!?njOBmo%N%%{7k^$Kz;gdow)7IsECeCX{V>f(=R#K^E$`dYusleOm5!!9&HrS|%CgJT0H9+yPhCF)ix;%PK@xnwg zS`V@2tH#o=VLk|UIPbZSpX^Q5H@shp(*K&Gu_-(L6dfwWYZ8o_S9a#EoACHtH^H=> zEsyQ&sQ^p4HHHIL2D5bC=?`Tn+2-0yBhi})n;u%56+WZFG?<{pLTy1OLF41^%SR?j z(xc!ZGHhp|mFfi-bBqHvrA~?^%}=o{v_I^!Cf_xlOXr*TV_F)S)^-T!`9r1|TIt-@ z%#%(0@mF0#^-~1Q5{8VY+belDN5;`qbI~ajt*2w{f3BEn(L9Z=9Sf9;co{swy6s~A zVTo_z_|wr{ow_1S0$E;(-?)R2ncVO&{KB#BCvrELLJ~ACCu>YRMfR3?R*mt66t+^& z@`nrcl99Z$4&&!*?qYRDz1WT0qq{jwfi=jL5RvRu4C1c1>^6L(Uig`*7#a~APS_;P z9lFhmmI{qL&CD8e4^=s{mXBqMqZrH-IF}S#lqmRhHifuck>T!+o@F*l+a=rlR2dWF zcd6)FCW_a$YZ=*-3xA%u7=*HR_40nFj`Y5NzFuW2Lm1;cKp}l2U@@&T_x2vw*RjNl z|vGgwvvW~~KrxiuN%_XCzIR;IP ziVHVY$A;v2tTA2XT6k}_FLpF#@!Wr{_=sT{9Y7H@9hxba-~isuaSHay$6e~qJQ*}! ztJE@KWw`I_=QB3wW{nB>*=pHZyiDI^B4QdTq*`4~!QL5j(R0$0E;DO*mZ509G~<5D zw1&%x2;sE|J(6c_-d2Io7dxEZ?&Pg%TeQsNUEIhUXak(V60o~(vny!{_L zxMb$ zA1qZ36{7EN*$OdP{+pLfrJO8wU{+EW8!5*idPE{UiA-~vm%~07S%S;xN brllja#X)z!G23(6NRa34s!3kve*b>~u#s-r literal 0 HcmV?d00001 diff --git a/credit-offers_mock_api/public/images/dog_nose_thumbnail.jpg b/credit-offers_mock_api/public/images/dog_nose_thumbnail.jpg new file mode 100644 index 0000000000000000000000000000000000000000..6b809c1f6ba8298d520b87f8c636bbe5b9fe7afc GIT binary patch literal 15089 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�cA+N|6QU{IbA21?l%%86NpM6^UDeTGWxb z++wt11y_^PeBTpK$xcYkjx6dr-mYY8G`YNfw&uR7&oklqEbP?aU`v{RP3z>83YWac zsKvbKr!&lIUS~f%Np1F%H$L|?y^Vafp>^J3_4!v5VUL#1XU-q{-uNw^vlZNu*i`uJ z?o?GA=aGLhth8+^@ys-h#KUV5Zu32^`RYA8JhRKTy?IH!i#6?+&k~)qMALJ9ob+{F zX4^WTVU%jP{ZR77OvhB?xG@96sjZFxUR0`!z!$MQmIcf1pUrPJsT+n0xGFZTX=7C{ zZc!CGD`)M2ifnax>?WRrF-ovhVO4%~4`_&|+3})hOe+XHunb?*6=^+-Yf9 zbLvWtf-{2$lC@>px|hs?Zl{HoUK!skS^u<{`f+`nx4dwZ*~+3kTq<0O9&ztp*ig$C z+H($i+^HvhQx@r(bmVoZ6XzBd(j_E_dnftSWcjIhlFPp>4AfF>$9mf;CRX{GV<_et zvs5NcICYCXEo)I$wQFO8PpKx>>4L87D4uETjT2-nPOUsAE*PF-4q0x0J?SE`U?xEt zA5qh|z;%))^7&O?OOKo5S7nTPIzLzCeDMfmu=LpJ#8S+M5up!#Xl0%dYJDcza+;t| z+>WNOSbMQb^qia7-NULuJgH-*PKr=ID>f)9_qn~%`B3E-Qjx4JN6W->kXqxVfmaCv zGA_{;xW{Ulw;XNFO(pd{UszIET>QYWtY;;-y8qhRZO!*{+~)p`GrYv|h!$e5r_N?a zj7fNpbt;J1%6v`DGAl5s$n-w5eP?np`BQJ9n#=O%^@Dw4&zh?rs~igDN{C0rcBbY} zjVz>%ZoUhy+s{>0ZD8oggAup&?F7roPNjwd@+s{g2*XXw>VLjFwIFjrf0!6cS&{Ks z+}7*1j&HQ%4N~zd3)z|n_CsG&G_40ETWAVrZT05K zgu|(YwhPuaHzi|^X)*P?G#8ayPtmvLRwM}>(d&L^hU4?iw4B|v5t#k7bZ*i0)vfCb zY^pJ)MuxS?2TGdGYrh<)&*L&UZ^gxLaFUqa!cD29cU>>KexNT{isGcFLg=F+PoY;2 zI4F@p#OU^Zc_-p}?wLT!S1tJicAMiokt_N8?T1<78)`W#b#9b)t?t9N9g2LtOmm7s zec{dNN=i!gYw@4FrF&P{f`*0c+vJ%Pwa3?vmm61bUYMqSj2SkQKOP{~LwRjZ*Vn=z zQqC~nJha+pVZrBY*POi_ zXTP!tC^wsx6!(o#dTXWOZojMyTdbLmJ+%E~X!f(|%VFtzEJLSXN2$E9tiRG=5sqsd zr&@3vt3m5@eO2JOR4|Z3L95Fg%;m%FaG|Z`toNii$7PqZ*g=-1(eFzSo%h8htP~pP z_}0|O&0D1HKcHt=LsjNWs+;R^2y>+b52?rN*Tr4%ZnI8^8}#j==Jz+;up?@jpp-c< zA*)QMByQ(d^sOcAn`@->{;>{q`e7zn+GxSHRB=6_P@6k)FJ6|aD4;G{H;4?(>-W`i zPk+b@sx&q(+tTSg(N%t|4$l+E zXWbpo?O3Z~Gn*Oxz^b0VyM>jddsTp~#84=CVeL@^1{bbadx0Q{P&b@nJSaKjYs$!hN_Db;E1Jb4ICpy*)+r%QZpo5rZ zpZYTcCCavJ177PBJoSobwG_{(xLP(gMRMsbc8cSK0KOh(Mdb8O^t^F-8}L< zYUw@lB(2JgO)lwh7z|B?aU7NNcV1$9`vMPLeK(N#vQ~OKbxUn*Q`$JQXPkN?Jj8~N zzA0!;t)5Jkea5KyM&FkO zd;6KZ5QT!ZmdZGvGa8sp<@wlIC4Q^r>f(E5mzXY*mJc(8qTSs2W*8VaYr7Xu7P(S| z2u%bLw9yZ|wX`Pp30*KF#`y|FQ*9XqGf4&7j3p;c-d4KA?m+7KX6hli8>yzL*jish zc6OE$tKse2!QqCD>TW?y^|Iuue$@0l%+^wE4EB%L#EckIc!9U{cUn!k{Z*_dzA=8b zU_Dm$#*gEDRYpMJd&foPWIkNR zeZB!hLuHLaJJx(yiD?gaDuh`E*AO_oeL`YMlzf)-J~H^A278e zEF(%CU-DpDmY#jQdP|p1Tc&15W0sk%IYA;Hb23+0tKo2x^m?7K?Uf{-clucOE$VMt zxX0bXuy8G2qb*kfpboB0eXL}fyR52sF3Jdr@zEf>g=o7lia%AAl z6z1v|Pg&)Cd>@+S=pzIleeHJ8>IaK-8B@0tEBT~gBYQ+aA|Izc-yoC1Ue=?)**5KG zt8wjWpUhxOUm3<<^deGgB5~s3eXQ94-41cZVqbjPomWFS?my(uKd#9<_5 zhA*|^nX_2wXh){fV}td5g5|59Xli{kiQktAUg^#Hys+_f)vLx;EQF>n zpL;4G%TMOOMzQyi(UAb2=7VLdbkfr2&s;cuocfaqS54bjL1dh$N$9ILG9njmzV64O zzi5r;Q+cZw#TF|b){$>D7)>oA(dWE)kURU5^n0bNGx;p`3(;gYrVO3lt$9}u_kN{| zTQ=r@rda1z{%-u$>;+#w>_e}NiNnCf zD*62B{*{{EI;Y2+*pT1K9Ont+KDfq7 zPo0aIWEkRne5u^*+y1vu>ztC`2Uivg18nk6iEm{~mf~vTke*;~m)2TEz4vmJJAtb# z1G97Aw+@N1+SKvn6CL@@>2^u){aBixFmRzF(|A45O37Z<@ts)!L?+v5{ z87xXJQTnb)2Sm)of3=_Gy7}ZoUYzlvjqfQGSLd1ZZO3LWnB|pi<#@AU&(f9Yl zD6^Jgnl%)_fts^Xig>rM5_79-+B@eNKZ|)9qes_c`;Waj6(Vr6_)9&CCqA`W;ENuS zZ|e>Nb~rKvcUz;n7dhwF#;w{$c}bj5FghxN!AaxPsh;k%kcZrJRVK#y@!M3^3Q!(eQnV@ld>&lO17!JrD#Uz+ci*{*xRjM>G0&K zkB(dAYWn=vX3$`)k3s%G)<7DrDKu^12=1hhH{gd50>hd!bh3G{(?Jkg(|!oRs@rAa?D<< z-}{)b-J|D2b~wG|darxt7bhQ_TT(F6kqjNVQvUX`({uNI^e*VduM7=&jCZ5ltzWD# zsB15t`{3pFWvgq+@s=`11dU5;F7@(x=rr(ZoNp-itAhm@^LdB*Vj7l@7sw-2cp}@0 zoK*5p^ePC9-@A}#Mrqhe>FJD#K|j?36(*OHioIxt6S-Lae&K}I&5|N9lBtUVm_+C! zK}>yuQe&2RYO(Sa<-vHBMV5~ITfS0;b6&|QDK7^ao1~LY&9aKsZRDD1(~rMLUwOw-PNuU)>n><1Ya6S^pMi` z8=zC|6izo>@K#C7SFQ@$D*5hRWwYiglzqg!am7L)_Z`h*Y3hWJ=}YCp<_WSVZA&== zDwVI5z9=)tk6?2b3}j(9aSNv7|pQ zqcQ5o2HOhPt0~oSACGPfAMV6MmaAbm%{=NbmeLw1i`;z8MI8$DSjvz3+~xyQf2@Eq{FdW1Y1RolY^EBlb{a z6JK(M2nTupE8V7vSR=E=v@MRYeueqZtByiPncDrT$mU3o`_}mJZ`>ao_bqwy>3O8Z zsRHGfCinceDFuob`xdTcdsqd|(S1Kz<`|hF%pMuWIs@&LbQa9oM5dh-u#64{GitiQ zeY(cffwHFiZubOqGv>NpWqllM<%rTrUJPtcf4ZplQd^wp)75YHSIux-R13F9lg~8! zvg6K8*sK)!unyuOD$`HI_tlU2w>mUAm~3qH$+j-b$52t=A;N^SxLM3-DyxkbYdp)l zkm@0cGlq{zrkeSN$VYF+zldm!%-r@+z`Wl2hK)>ojCt0OYui_u5tVzT&Q|7>-W2ha z-UCtgX(1I-PB>G_LgiP%ZX4FzjtQm8Ng zh;Oeldcm>!v|X>!PEuyE$n`HNCX2|^`@aaRKWH~iW>H0E2E16bKV_&O?@7@lcjFT{ zY$1Oy*eROu;oOBXhm8sX>smzt1~s+vqRz&)8Ok+$_3(kZt^H`2ORY21607{+$^b1H@R>50j*l$N=% z+>_I+2v1ka&-*jU1C^-4%lc*^}<*VZ>Uy*BZus5(~ zD4Gh_0lpHf*_bgYi4Nef)y_G#a$ojmG>Y_Fl$RuQ5}vndB{wF0l)M$hY<+)KM;XTz zb(`+iLh(kmnk{$1@vI6-{}*pYCo2pRqVyDrcMR9`0!B(c%rq?=H5{=i?d+)LOC$>> zb@{k?i*1{6_iy7xx+ z^E;)=?fccJERlO`AuFB6F&xU3Avt`OQ=FN^R zjIPYip4P1DPI<+a!9$bqosG>-xhOhJ$jYjGabfi(B~^T>n(_(e-d90&S2{N>Dhqn9 z>X1iE1?C^YLjk_(wGV5$xo%+$?(%IoS7TSUvO9JJK6?cxO_^SprxUe|=@Lh)f1Beo zZMIui7;IK9Y)RqVUVCgiHNj8S#=fOBvz)Vklh1=_V%>e9>6GSn#vrTjt%nBd;Ziam zuCHz$%ht>f{H(oyrQz9@z+^!6T{+p`!}Bh!StogGZigvINGD6Wh1us^ z7~(@HOgFB-qKFbRlk-^LAyG;5NW5`7Ti)cFZuu3HyduwnAchU;n2e-pp7@6W9X=!D zY{8|{2gzI+({BX3sXu_WBAtCXx|JHAZa-l-q?fGzexc>0Tp)iM`}xnlF20?91*N%* zjtwqzjH@ejUY6Y&js((cn5(A~OgiPiaZNi_KSPs6-yMxkT{+kzSp0E3GOaR7@@NVs z`)lY`v!ocRI^P;Sqix#l3Z81P0(~)Vo495pTpVyDPEkb>LLd;3G6)c6 zjQQnRcS|z}I)5JGf*=SVB0`WsNPuubj6{%QF%`g%5Jw;!@J4``gP{M33lW?U3d;wj z5Q12Fa*(b-2>mSEf;frAa9IdR`Agpa(Jy|GC4|Q6fuK}; zf>{0I4s1p``X~Oxo_CM#+XGs0mt!BBLeK*U!UMIS1%!kIAD|A}g7klY{)uBE5I|>#`xtmDlJKYeyGU0rl0k&S0fCP|__zg$?!cPut78DS8g4ov&y#54v;9DyBC#VBVa`Oq!0cK`+N_t=e zv-DH4pZLsE@_jv^f71?hf`+Cc_xUhJ{f2RW!z@_aSV4dOG0O-M{O}KW{vdcfNQ0;Z zBH>^4+F$XoBNHx(^W?`E01yz&IRw9t0f>SG0jvUEhK+=W6$=>8u#qt41Q-qQQGf*i z#syOF5O)R`4`5n=?*dE!Fblv>044?{;3{0`4<93eI|cCGfJna2w?{%Z;eie7e?tsG z)cbt6I33`_W#M=nU;*%c4`c#<$v{zne+2M3Ngya1)T;>aC4gf9hJQ{^0gi?11r@vm z_|bk{FDW4CF<^ob4wV7?1n@}#_6NSm0vMPGB>`Lr@PQ5OztuYm_@Ee+1!NHNC`bzU ztOa0vfZ?CDJR|VKK2`*nT>gho{s5Rl9uL|9GB*G|w9mI-0&NF;XTZM;@L_p0)`lH` zX#j?&7Aj^4q6HY%&j2tTkO$ciC%`aW!%D%q2XtULoW}T(fs(Ku6okPDK;od=5uiR6 zSO`lPEd8E>F~;wJkHNr0rT*yb9|tmL0C)nm124V7vf#S$K}f--ems7tFN7+fP%L8u z)PA55X8hGmrD*AB<95~YI*ZPAM<+`=TNXhuL5cm)_dk1PWtodwh$=&&Ay_8?QBXVF zl_`<25wP(;4MUWdgUKAGA5Sd&*T)lVk`PG-y@rTeT7G!$Uquj@U!^a=KZSjQxd##U z$w7p(f0Y2E?Ke4?4+i%y0#5ps_|FJ&>;m|O9D>9nTq5BC0o(iU2(C*W*n1QFA{fE! zVh%3tR4YS|~>xCoSCiqwTnmZu$%ZX9`0C3uDpARN_FzSBA|6&8k`!n?+fcfYoxG8^|{$DHu`{KYK zaGxE}x2oJ>IKt`Qo$xy!?%xCYoqxcC-+zY>*ZWs;zuWV>&i@Yo76b<{PyNfR^1HS_ zVbJT?@F(_vN&a6V*RJQTp$m*V=!-wUK^DBwVTtP~_?;9Tr2c<=CqsCUJ#+Y?73C9SQPKJ-@V9`D zf`Ev)sE81}7y5_(@xVVG_{RgV2i}7SuMF@?&jTX7g2G!aI1Ll(H%#^$hFAWd@*r^_ zkNgdze#bzb1w^+^gQt!68r6*`Lcc;QpwM7uCq*C;^uVwcJP8UJicta_^htymf)5IW zKB0P0XK=gmy~skyI+4cso)nH0rwK*SN(d!{Bsg!*qQ0Q?aYYH@$&$$UP$FW%opyI9|_pwE&0U6i(Gfk;9<5h;gJnjR~d=(SRJK_Q#APvXB?33NX6}59FmoGZ;3cGSY&a6;eYoB6~?S zAy?!&B8Mad%0RXuZW32QACb9;5~67ciHb!m66zrCpo|e$2v!i}_^j}u5*x_D8cYgK z-f*A>Hx3lw27&^+0Ro3_Kro*Q8>qpP8k~n<&LJRi2%-cx1&0BT8e#-*8f>Hlx#S=R zejf(qj{rU`$Ug#}lt7Xao9Ob!U}Of$3WnPM8LfSjE;dE77!tb58{VJ z!L0&6kb@&P#0jKNfOs5WA?PF+tzzKfMjVm^rNw{_PKX^uIF}!kKL^Q!paSAakQM>@ zIKc&@AkfK%{hb~KXFL{A-%(%%C*ZIEJ|jdAO2QEW`gkE>Aj1XnPJkynkYEOZ9!N2Q z+X5+2rwDWwXcWfUdK~Cs1Tst@z{3)*Uk+RoN`V@=fG!S@#|r9T10I3z8km5c3|M_f zKn^?BU$7^CTq1#;5(wO&HLM`8fItuY2z!(U_>u-l3xQg>f$n2K<|xq00P?6oc{rB_ zXpsTd2?86rKt2=D0q4Wz7_q$p1pW^b0>S~ObSbd6!~+8o7hF-IAtLNdhQwOdhgd_& zq16dvpdiQu^g%Fat0WjhNFWX0BrT&oaf=9Wh*Uuw zrXcJXp#^?83fg}NNMLWz4qAT`05BgwXbHk8(mS*$@_dXnW|b5o*MX8DR&X(e0ooE` z(NL@p8k+S&K??m?`5%+=z7`BKgn%P9iHt**Bl-|Y$RNbYpT{p07y-Iq$D9py0gd47 z4(^!3p;EAGhV?^`JGLk}=x2Gb=e~)(1=0X*TEzSaUr>Xn1r#p&N4N_w4EPw>abU(@ z=O^a?j`@fF@xVVG_{Rf(=>hl#)EDZ2w~9Xkbo;lI1IW9LT^~vT7Z0pPy5QSD29ygG zV`r(3+CqJctB8QGf-&H`aYQb@K-Ig0p3Wht0v;25<%*R-~y8fiGV;6*hL~xC?qfz z2MtCW!Uz6*j;6pl#44zOOQ~@c@2CTn(5;9Re6~{sZ>cpuF0u<>bMz-5JWNANcjVY{ z4$c!?BBEmA5|UD<&zx0MQdT*qbx~VKS5M!-#PqtExrL>ble3Gfo4bc+!0kJML3i)n zkBoX49TWTLaop3?v}e!LGcvPYzA7wwU0hOH_O7P3uD;=YV^hbc&aUpB-oE~^@rlXL zQ`28&W|x+~udJ@EZ)|SC?E;^be>Cf7%l_0Z3eYYj=mRt!+%5#t4Q@CE8i!R7_mF}H z-c<+6qe8dvsZK?t6uc#16V_a$zUKIm@G!f`*s&$JY1o$iy@vV!FSYDv!+y1^A0h@< zDo6?x1voD4;zlA;FNG=!ZdX1%S;yG!95tJ`doig*!-%Z!@LE?2d)_02&gCqHru=m@ zQZ{F+RMz37lIifGJx8Y4xowVoU^;j2(Zg#({jB_uY_C3f$aIp%s}-~k zU(}M&?8I6`?3*ffBh`0Og}5A#->W=#PlK{~O0NuxUr>I}mQkmRf&7CEH~iLkq|}Oh zQmvENKf2s@@89x@j{W-SxxfojnkkZqLPrJ0((g-$oG_3@QbL*K^vE6^vv@6oTu?*F z`Kv~r5&hCl;#FB&xir?RePNwN#(~51OU7$2Rd%0cD9UwNy534rTBU!P@fY@mu;rt zNj)hg7Mi7J#eX6shsU1ie3jBD)Q87l@~PUOC-z;I8?GWZ+hf5&=A{>)7SV4C3>aum z(e9jE7@yw;rC+v;i?5{k-3jCQJYsiaJzaTKdgB5$g_-RyVJbV;92bJ$_>r$AL zRVTX4(`Gk0OR@X3Tc0KnsZAfPgkl$zyW=r87i)l<!(vXv6sdU4NUdv2ZS1+$cPKDFTEE>4~G z3H)--^IrxlH`rHH?0ih~oSbU?NQ%^P6wF0C&*n_M8!$GA;4K_2kXEih1+?!BI^tQ= zxN&D&EGtTvCR8GK+!#}}j_{LG$*<+_%UyH_-k|nNn(jnSC{QZ8 zBl>Y-L+7G!h3e)Cv4*a@$xn#VhJWau>h7V2W z&$Z5^I_buLJwdNB`89!hxAT7U`?Wo7^kvGDl6$6s>O=WWcN}hQZEX`~?mhaL^Xe=~ zw=%IjPwFR*?!rRr_s(;2d}49bJcd^i^Mz*&mCjfy=1^5md})LBT4)T0U*vC0Xj+Ut zM#pTKJ(}Y7P+DSZ5~vGuF{*H+yD70APP##(vKST^=Nf#~GOY$hzG(l)y$aEor=Pb!9+;ilyrt_YtwWyc&}M< zfqQyngG+Ay{M9A;J%U$bC$%5*@fVFy3>Y@j$qCmH3pM$PQM~Y(Taj9p7>}5W6pidR zUACRLT53c_e^=Tek}NB#2h#nVH@~@kl!QoQiKE6x=63AiQTOb(Z)8pJRfCG=5+1xf zI-RNf!NH7b@al=0d2Zoqo?O~%nVp%12G!|)Q9T{g3X5}NL)A5Hy~$%Too ztMW$Nx9V}-xr9C0GcFLF;neEjN!`qcpR7r6CjB_wpWRQ_z3&jsQq?g~cE5UJ_fk1A zv-x$Ka>I;CU+ok-@0|F1eIp?{%4OQxI=rPpzEUfq=UwFS_|he{-oMQ=6EvT8j1?|O zwRLNm2<-2}V`KLswCQ|_+h4ID!Lrt7kWKo+RK9?@HB&aN&0e9IdgA`q5WW_d8wCv3 z9@%W5WWGq-G_AV+ z`tCaZZRy)SdUY7c_k&_w^22($cx(Ui0AjM5N;T4sgxPi1%MqWxmoAuVM)Qc#qjn0W zV>hx4jJHk&jXOtFxal;NUVb!5idVgyBhqmtZcTae!Hv0cyP*ZiwY5o|=&(NC$A1J& zZ@&;;PU03DKVBSvjjPZ~sA_XG*>mOUi*S?aFP-+035;>CD>b?Y+>5u3Rl-%5h(B06 z*{1NUmV_CIGP`m=+mondXBILq8Glt2Zy3z0cGBmZXmMw5C*QYfnR`8WTc}Tao^E9=xmm8bu;`VWNQvwRrrxeo>-cK) zv%+l?I)7XU2xp&k>9^rH0Os%*J{H(K4T{A2Zu#fO(YB3>)~LCu!IvW)*lVe&|^i{)BT zXrwyVIAs}~U}AcE*sLeTNbhKf{tY_YxaGPSqIwLZm%EoWN!s@9OHIUrM6L{nu91r2 zXA867dsqFc>Gqziu>}|>4`j)e7k`XDqL#j!LS$>q=N{H)((1~bXYUy>=*e(@$8WdV zS8R@XalL$==ComaUcSy7-o59c@CHXC&3{k%+@vDq2XTZP+O&wlv^xG*NG=s2I-<<;+;aE&o;_&nWF z6~Gr;{`GT(+b;75=6Y$D*mp@+cT-QPT`VBg45|23IKDH%mzZFzS$m-{$JeLAbv5I9 zN2KOUW|0$nq!{S&`kpp}TYfKZ`6WrcDF?diUrN6iwo~3p*5vA#B}m!G!mSime=?|y z&Pw*}-O8)ZTaO)5puXFhzu|d5s%y3Qbk|rifz#kLLb^UQv%bea`cr0}T2oJ9(n2G* zRs8XAR?{%Cs1}pLqezc=!^j=cuENkG-OJ0IH|D>|b<#1MJm(to=Hg(Vgv9$7FTU3p z80~oSeh$ec>*fE@|Hy&u61V(&p>(tBH@>RovoU!PVRajh?P(i$_Xv81m1=lhG*>OI4i&0`7Ojz)t znD(=m@6^|+l;)Gh$-8_uZAPcZ^}7ONVSaCY_4oI(gn1bcOiGyw zHQLxt#f7t3aeHM~O(#CyAbC7dAY5BRcmABC&DnYt_BGv}GdVHwOcZG|hkENuk8hvK zCVy`@wI-)-Rgq0HImI!Xu=?gqn&SuBc~Z>D%Rt9U_?c9oY;k#B*GYmaEA-{ zY~@Bdm2!eBd8z65wa#hG`bnAO7YUTy8=K2MN0MGP(3rM5;~e4@6RuWfb0^kvpjs}1 ztx_%Sl3V|oX7_^kk?AIL$)Bhlzn#0U!|H41Q0QISHl}-Q@pjyq8^!S{W|s=hKgTJA zNi;O!FL+Mh8no16B(zi-kLu@k+fyy~Wb-cO$quemE+Vvl6|G{VC{xr`DjN|xxy9Qg z#rVmp;B_#2l56U-hN}7t1EE9t=toB7jpXPT9w!>u316BuZl(?nHReovn8ryyA+d8X zduM2N*Dup$l42)Z)6q#sR5iT#5>dR*#~NqpH}^s_`dB+n!>9u|R|&~H1(>=M_VNrG zi`)lH^K$HZV&;s*2Tsqp2hW!5$gRB~d%P^FmKq%uWwn&i@zH6yQPj`*Z9j?5Rik^r`LVBPsgQj#bC;x=X&As?|vU zzN5Rz^qH#}FKT?rF3or}c{6Edym@4v&CSP!-TS@m<4M$wGmP7ze5oT7ReVG~<~70=>*Bn`ZXctBvNs(?uB_k< zPRI^2k$9-ZkCJ%3k&@kInmuo*S`uaX+Boqo;fL=WLv|0vXs?9z9xr$LqxE8T$%Wx= zyXEa_ttDn6Z;AA{$^1I=^Gy%4GT$-E46%YaOS=|g&C*zsE2 zq4L+1Z#7fdm?{g})U0KBZRr-&lAW2X^&?X6^e*_MnWos52Mz`d@_E#lH15z(1ncFs z@Z0y_h*Zv&rlkBVI27XVd%fPh)xFj5`AHkwx)raxZHa5yswwN^HnpTFU4a+xu(1S$ zC0b8i8%!43O#T!ZDxoOg|MFZTZ{e5f(;t0Fb}mMZb-&&^ob|rnXIjjSveSMZhQ-qZM?&pI2}~dD7a_!>{ZVcb=%IhrIT%A z=TWNq)YZjBg{C3)Eny4I2^gs7>~w$boYV2(ydkcMmWtPwH$N9QlQ)I;HJ7ky+1z}Z%V8>$`n&8v_lhW z-IHaN8LsxTu@f!rr?iO8HPd7y;W5v&;n{`wxxmRU$bXI6wANA>U4FjQG>Zm@IYa5(nyqB#zTa@y3LZvV>ts|f^?p0T_x~HbCT7R*r zlAn9`Cx<)XrO0yrz&)|WJ3&GHGS`G`cc$1T_ZWy>Wmn%D={LC9aXe1UA(M@6bdPSS?e&)qp^BhuA1 zKEYY7ZNC0$W$C&__5GBx!oX#9$>P(_sYKFBH*R_*g~&XjnAbl%EZbp*YN;^Pu?=<( zwN-DIFsRx}a2(i-Xk@snc$ynY;=N-VKHGYxEmUr_bXcNnvF9sYuHJc(LwuFldV1X9 zL+`h*i_@Q5;aI18&z7!WOyT!dU|LG|EkFGw6Kmsb^1He~f`x86CYol+H~mypq5`#o^smCcDwA-HW`Eeh z3JjiMsl^`}r;it|*il)Q)w)s49`UJck6?F>lZ#aT?8;wSl@^tRXnW?M6PdG_Uf#JC zQj8+TWlEwUt!#48P;EMKY=bSjXRl!*xZCK|kj=tP$$nSm(h0}njqBE89nG~Pq&19U zvo@#RC$^EcxBAULZYqAl-KJG%P;r0U$kE!T%B5Vj&K z#%E?*ZAOr$^|BL-xe1H(mJ=~1bsjq}&llx}+mtlwf9Xpe%M0GR{L1xlqI0MU?cTzb z`a|x-PgXCu)7}UZt{_iy>z_&*X8XwdjkniuR%gN`Cu>JmF2G%aJ0jZNp{-h)DP=5_ zXk3Jsp}o-UZk9gT!a^$JBY(s|4<3%gr|!~+?K>`uaVlvaZI5*pcAtzJ>an+%n@Lub zxkbm8hg)XR)nlY)P-uL5))>dz%=qr`;g^*q80hqpc#t_6=K~z!o31esMc|;W{d~x+ zfIG8$lpCq)pX99Pa!qzwa|%Du+oGV}=nEl}qa=~VRWbtH6Bvlh+oPbnsN8FF+>Uhi zV@a6Xl&b!%QPV3gAGp6IraeT8XY9KnIlA3;Kbc~$>)DV&|4e^f!UZdFRj$Irs+5v$ zWQorRAH93+!ATY6Z~ZY;xzVxBwwf*Ult$!3H{(Sz*CnNTZsV|Fo^F1V0h8yv#cMtl zlxh(bLm?>&Z7k6fRqdpjudZ(JyD&vFc1;+#o1c2Au)SSJLEG-IT<=g^(tPFn8vda)ue3deHTcrkGHACsRsJ=hPJ9nCHe8!3>Vt*O;(l`2Z|My6h%^U zx5O%U)Cs&1q-s4Ey%}`rhIi6g!H8Uw>nC;XSi(jzkijm|@z@3Ns%p2`SwFIk59b`l ztr9vP^3xV}eG}d3_+IAl(I>iUDk=U*oh@biM30<7;FkORbbrT-D>*x~amo!iL9`m>d;L z*hU!kv{SNfEb+yPwm#_gvwC1EEq~fRc~OAu(?W8d=cq^}A$KBc{4wPVJEVn4be(ZT zjfA0jhesZ(5wu~Ttm@dtj7Y}iQWpkGk&`>^gLLI%}6m&u8z{IZ9v zGZz8v(DDgdHk33ej=5$6qnZb=%j~w^*HlIA6JAou#0J)9Q`Kg6lAbpN@T_cC{i0o|4Ov`6$ZMUHTQMifkEf$VSRkUTsFnic0op zPcN=Djhx?L2K_de6Qw`XUU5kL_|XdiAzPC@1U8w4{nH_lXTp?CJMTBrzxeF)`t=t% z7H+A|%#5?gUL;3+(M(hl8~9}0vdT~99-toMYL_ILYxAKoLhR0H21}m2qndycWbfH? zKHI(dY>$n|c7{ou%t!X)$2KF@`H5xlS(1H*2RC)%({SRpWHyIPj!fp6Map|&pnUG6 zr52*Gxo?o(8F9n6c{RjV>VpL6lIg{q-Gqy-U#&mz+33#~fLrt0t=t!nJ?xxH&GbeS zR6keXy9TTK3bl>KRes4jE>0pnkzKP^djXn@dTdjs8oz}kKjZ$ zzTX(#lE{Dqd%yFr398xV&dn`d(I=;RC}-peauKo;@ zRQTHwauc1%)`iyD69$x04qx^zAHb`|Ge8)?se6%ja#-< z%6ZTAGIb`&GcKTJ#X_7q-7>v%k9SuulCaS^i6pN&`yjG>yDs3bl*^prR=gLZ*I0DZ zkpDe`>~mz_P`#t)O2fIZp0$}p;~-PLRcqB?>XFavF>g)XydR|6H9;r!re)mo9`CWv zsHATczs)K4wfJVUhzn^joI!jQkjRtU7)ouhPHoDfbV*93Um)_|P7L-n=a5}V56Q$Y zqPN^rj&nb9X|R8;Dt+iOC-{O9vcbemHkio#uB`i+-%+aKq$!IO<=#=--kjRinTSEN z60kPgBwm}xKw#}z6!y+IR&K^oERR?1xv?@Qe&mWVpJHurd%`@^NVL_=o?xWqB^A=~ zT29ubn@Sxea>d^zxV19v&cuH4*i1((E|?pLerFKMB;N%PBkdvOGt{Y1v)R9_Ea_&Lru$nYfW!y6^%+PHpw}YZcMQBjzWxBf4l2A>BS} zl@4}}9~;=X(wiXBw_0K?&v&=JW1u(5n~5ct3CD7ziWqRHF_8V3UsaDBrtg0Mc@X5z literal 0 HcmV?d00001 diff --git a/credit-offers_mock_api/public/images/water_huts_thumbnail.jpg b/credit-offers_mock_api/public/images/water_huts_thumbnail.jpg new file mode 100644 index 0000000000000000000000000000000000000000..4907072c287b36aef8367fb2da5ac10149a58a65 GIT binary patch literal 8060 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 L!m(&Se(e7NA0x<- literal 0 HcmV?d00001 diff --git a/credit-offers_mock_api/routes/index.js b/credit-offers_mock_api/routes/index.js new file mode 100644 index 0000000..d1f8d1e --- /dev/null +++ b/credit-offers_mock_api/routes/index.js @@ -0,0 +1,158 @@ +var express = require('express'); +var router = express.Router(); + +var fakeProducts = { + 'good_credit_1': { + priority: 1, + name: 'Journey Quicksilver Best Credit', + tier: 'Phenomenal credit', + terms: { + primaryBenefit: 'No fees, and great rewards!', + purchaseAprTerms: '24.99% variable APR', + balanceTransferTerms: '24.99% variable APR; No Transfer Fee', + annualMembershipFeeTerms: '$0' + }, + image: { + url: { + method: 'GET', + href: 'http://localhost:3002/images/dog_nose_thumbnail.jpg', + type: 'image/jpeg' + }, + height: 93, + width: 140, + alternateText: 'Journey Quicksilver Card' + } + }, + 'good_credit_2': { + priority: 2, + name: 'Titanium Plus', + tier: 'Stellar credit', + terms: { + primaryBenefit: 'Virtually no fees!', + purchaseAprTerms: '26.99% variable APR', + balanceTransferTerms: '26.99% variable APR; No Transfer Fee', + annualMembershipFeeTerms: '$0.50' + }, + image: { + url: { + method: 'GET', + href: 'http://localhost:3002/images/hammock_thumbnail.jpg', + type: 'image/jpeg' + }, + height: 93, + width: 140, + alternateText: 'Titanium Plus Card' + } + }, + 'average_credit_1': { + priority: 3, + name: 'Gold Star', + tier: 'Great credit', + terms: { + primaryBenefit: 'Fantastic rewards', + purchaseAprTerms: '27.99% fixed APR', + balanceTransferTerms: '27.99% fixed APR; No Transfer Fee', + annualMembershipFeeTerms: '$0.51' + }, + image: { + url: { + method: 'GET', + href: 'http://localhost:3002/images/steps_thumbnail.jpg', + type: 'image/jpeg' + }, + height: 93, + width: 140, + alternateText: 'Gold Star Card' + } + }, + 'average_credit_2': { + priority: 4, + name: 'Rewards Max', + tier: 'Good credit', + terms: { + primaryBenefit: 'Great selection of gift card rewards', + purchaseAprTerms: '28.99% fixed APR', + balanceTransferTerms: '28.99% fixed APR; No Transfer Fee', + annualMembershipFeeTerms: '$0.52' + }, + image: { + url: { + method: 'GET', + href: 'http://localhost:3002/images/water_huts_thumbnail.jpg', + type: 'image/jpeg' + }, + height: 105, + width: 140, + alternateText: 'Rewards Max Card' + } + }, + 'bad_credit': { + priority: 5, + name: 'Bronze Preferred', + tier: 'Bad credit', + terms: { + primaryBenefit: 'Low credit', + purchaseAprTerms: '38.99% fixed APR', + balanceTransferTerms: '38.99% fixed APR; No Transfer Fee', + annualMembershipFeeTerms: '$0.53' + }, + image: { + url: { + method: 'GET', + href: 'http://localhost:3002/images/chicken_thumbnail.jpg', + type: 'image/jpeg' + }, + height: 140, + width: 93, + alternateText: 'Bronze Preferred Card' + } + } +}; + + +/* GET home page. */ +router.get('/credit-cards/targeted-products-offer', function(req, res, next) { + console.info(req.query); + var creditRating = req.query.selfAssessedCreditRating; + var response = {}; + var status = 200; + console.info('Determining response based on credit rating (%s)', creditRating); + switch(creditRating) { + case 'Excellent': + response = { + isPrequalified: true, + products: [ fakeProducts['good_credit_1'], fakeProducts['good_credit_2'] ] + } + break; + case 'Average': + response = { + isPrequalified: true, + products: [ fakeProducts['average_credit_1'], fakeProducts['average_credit_2'] ] + } + break; + case 'Rebuilding': + response = { + isPrequalified: false, + products: [ fakeProducts['bad_credit'] ] + } + break; + case null: + case undefined: + case '': + response = { + isPrequalified: false, + products: [] + } + default: + status = 400; + response = { + code: 999, + description: 'Unknown credit rating: ' + creditRating + } + break; + } + res.status(status); + res.json(response); +}); + +module.exports = router; diff --git a/credit-offers_mock_api/routes/oauth.js b/credit-offers_mock_api/routes/oauth.js new file mode 100644 index 0000000..6e2fc7b --- /dev/null +++ b/credit-offers_mock_api/routes/oauth.js @@ -0,0 +1,55 @@ +/* +Copyright 2016 Capital One Services, LLC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and limitations under the License. +*/ + + +var express = require('express'); +var router = express.Router(); +var request = require('request'); +var url = require('url'); + +router.get('/auz/authorize', function(req, res, next) { + var clientId = req.query.client_id; + var redirectURI = url.parse(req.query.redirect_uri); + var scope = req.query.scope; + var responseType = req.query.response_type; + + redirectURI.query = { 'authorizationCode': '123456' }; + res.render('authorize', { redirectURI: url.format(redirectURI) }); + //res.redirect(301, url.format(redirectURI)); +}); + +router.post('/auz/authorize', function(req, res, next) { + var redirectURI = req.body.redirect_uri; + res.redirect(301, redirectURI); +}); + +router.post('/oauth20/token', function(req, res, next) { + var authorizationCode = req.body.code; + var clientId = req.body.client_id; + var clientSecret = req.body.client_secret; + var redirectURI = url.parse(req.body.redirect_uri); + var grantType = req.body.grant_type; + + var responseBody = { + access_token: '5354e3a56036056cffb5a99f368a31cef3aee2a8', + token_type: 'Bearer', + expires_in: '900', + refresh_token: 'cV6tIa3UQncpzGgXfufRwZJvVbwZeoQPpsx7YzxdYNY', + id_token: 'eyJraWQiOiIxNDM4NzA2MDM4NTc4IiwiYWxnIjoiUlMyNTYifQ' + } + res.json(responseBody); +}); + +module.exports = router; diff --git a/credit-offers_mock_api/views/authorize.jade b/credit-offers_mock_api/views/authorize.jade new file mode 100644 index 0000000..72926f0 --- /dev/null +++ b/credit-offers_mock_api/views/authorize.jade @@ -0,0 +1,9 @@ +extends layout + +block content + h1 Authorize + + p PhotoShed would like access. + form(action="/oauth/auz/authorize" method="POST") + input(type="hidden" name="redirect_uri" value="#{redirectURI}") + button(type="submit") Authorize diff --git a/credit-offers_mock_api/views/error.jade b/credit-offers_mock_api/views/error.jade new file mode 100644 index 0000000..5b948fa --- /dev/null +++ b/credit-offers_mock_api/views/error.jade @@ -0,0 +1,19 @@ +//- Copyright 2016 Capital One Services, LLC +//- +//- Licensed under the Apache License, Version 2.0 (the "License"); +//- you may not use this file except in compliance with the License. +//- You may obtain a copy of the License at +//- +//- http://www.apache.org/licenses/LICENSE-2.0 +//- +//- Unless required by applicable law or agreed to in writing, software +//- distributed under the License is distributed on an "AS IS" BASIS, +//- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//- See the License for the specific language governing permissions and limitations under the License. + +extends layout + +block content + h1= message + h2= error.status + pre #{error.stack} diff --git a/credit-offers_mock_api/views/layout.jade b/credit-offers_mock_api/views/layout.jade new file mode 100644 index 0000000..18531a8 --- /dev/null +++ b/credit-offers_mock_api/views/layout.jade @@ -0,0 +1,21 @@ +//- Copyright 2016 Capital One Services, LLC +//- +//- Licensed under the Apache License, Version 2.0 (the "License"); +//- you may not use this file except in compliance with the License. +//- You may obtain a copy of the License at +//- +//- http://www.apache.org/licenses/LICENSE-2.0 +//- +//- Unless required by applicable law or agreed to in writing, software +//- distributed under the License is distributed on an "AS IS" BASIS, +//- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//- See the License for the specific language governing permissions and limitations under the License. + + +doctype html +html + head + title= title + link(rel='stylesheet', href='/stylesheets/style.css') + body + block content From 36167366033c978cda963917d408800010cc874a Mon Sep 17 00:00:00 2001 From: rkorsak Date: Mon, 22 Feb 2016 12:24:45 -0500 Subject: [PATCH 002/147] Applied linter suggestions for JavaScript Standard style (http://standardjs.com/) --- credit-offers/app.js | 67 ++++++++++---------- credit-offers/config.js | 2 +- credit-offers/creditOffersClient.js | 88 +++++++++++++------------- credit-offers/routes/index.js | 12 ++-- credit-offers/routes/offers.js | 32 +++++----- credit-offers_mock_api/app.js | 63 +++++++++--------- credit-offers_mock_api/bin/www | 56 ++++++++-------- credit-offers_mock_api/routes/index.js | 40 ++++++------ credit-offers_mock_api/routes/oauth.js | 60 +++++++++--------- 9 files changed, 208 insertions(+), 212 deletions(-) diff --git a/credit-offers/app.js b/credit-offers/app.js index b241681..9244a89 100644 --- a/credit-offers/app.js +++ b/credit-offers/app.js @@ -13,64 +13,63 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ -var express = require('express'); -var path = require('path'); -var favicon = require('serve-favicon'); -var logger = require('morgan'); -var cookieParser = require('cookie-parser'); -var bodyParser = require('body-parser'); +var express = require('express') +var path = require('path') +var favicon = require('serve-favicon') +var logger = require('morgan') +var cookieParser = require('cookie-parser') +var bodyParser = require('body-parser') -var index = require('./routes/index'); +var index = require('./routes/index') var offers = require('./routes/offers') -var app = express(); -var config = require('./config'); +var app = express() +var config = require('./config') // view engine setup -app.set('views', path.join(__dirname, 'views')); -app.set('view engine', 'jade'); +app.set('views', path.join(__dirname, 'views')) +app.set('view engine', 'jade') // uncomment after placing your favicon in /public -//app.use(favicon(path.join(__dirname, 'public', 'favicon.ico'))); -app.use(logger('dev')); -app.use(bodyParser.json()); -app.use(bodyParser.urlencoded({ extended: false })); -app.use(cookieParser()); -app.use(express.static(path.join(__dirname, 'public'))); +// app.use(favicon(path.join(__dirname, 'public', 'favicon.ico'))) +app.use(logger('dev')) +app.use(bodyParser.json()) +app.use(bodyParser.urlencoded({ extended: false })) +app.use(cookieParser()) +app.use(express.static(path.join(__dirname, 'public'))) -app.use('/', index); -app.use('/offers', offers(config.creditOffers)); +app.use('/', index) +app.use('/offers', offers(config.creditOffers)) // catch 404 and forward to error handler -app.use(function(req, res, next) { - var err = new Error('Not Found'); - err.status = 404; - next(err); -}); +app.use(function (req, res, next) { + var err = new Error('Not Found') + err.status = 404 + next(err) +}) // error handlers // development error handler // will print stacktrace if (app.get('env') === 'development') { - app.use(function(err, req, res, next) { - res.status(err.status || 500); + app.use(function (err, req, res, next) { + res.status(err.status || 500) res.render('error', { message: err.message, error: err - }); - }); + }) + }) } // production error handler // no stacktraces leaked to user -app.use(function(err, req, res, next) { - res.status(err.status || 500); +app.use(function (err, req, res, next) { + res.status(err.status || 500) res.render('error', { message: err.message, error: {} - }); -}); + }) +}) - -module.exports = app; +module.exports = app diff --git a/credit-offers/config.js b/credit-offers/config.js index effda10..1cd2634 100644 --- a/credit-offers/config.js +++ b/credit-offers/config.js @@ -22,4 +22,4 @@ module.exports = { clientID: 'COF_CLIENT_ID', clientSecret: 'COF_CLIENT_SECRET' } -}; +} diff --git a/credit-offers/creditOffersClient.js b/credit-offers/creditOffersClient.js index 7a08073..3188ae1 100644 --- a/credit-offers/creditOffersClient.js +++ b/credit-offers/creditOffersClient.js @@ -13,11 +13,11 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ -var request = require('request'); -var qs = require('querystring'); -var _ = require('lodash'); -var format = require('util').format; -var debug = require('debug')('credit-offers:api-client'); +var request = require('request') +var qs = require('querystring') +var _ = require('lodash') +var format = require('util').format +var debug = require('debug')('credit-offers:api-client') // Default to a secure call to the API endpoint var defaultOptions = { @@ -26,27 +26,27 @@ var defaultOptions = { apiVersion: 1, clientID: null, clientSecret: null -}; +} /** * The API client class * @param options {object} Client options (host url, API version) */ -function CreditOffersClient(options) { - if(!this instanceof CreditOffersClient) { - return new CreditOffersClient(options); +function CreditOffersClient (options) { + if (!this instanceof CreditOffersClient) { + return new CreditOffersClient(options) } // Store the supplied options, using default values if not specified - this.options = _.defaults({}, options, defaultOptions); + this.options = _.defaults({}, options, defaultOptions) } -module.exports = CreditOffersClient; +module.exports = CreditOffersClient /** * Perform a request to retrieve credit offers for a customer * @param customerInfo {object} Represents the customer info to pass to the API */ -CreditOffersClient.prototype.getTargetedProductsOffer = function getTargetedProductsOffer(customerInfo, callback) { +CreditOffersClient.prototype.getTargetedProductsOffer = function getTargetedProductsOffer (customerInfo, callback) { var reqOptions = { baseUrl: this.options.url, url: '/credit-cards/targeted-products-offer', @@ -55,51 +55,51 @@ CreditOffersClient.prototype.getTargetedProductsOffer = function getTargetedProd headers: { 'Accept': 'application/json; v=' + this.options.apiVersion } - }; - debug('Sending request for targeted product offers', reqOptions); - this._sendRequest(reqOptions, callback); -}; + } + debug('Sending request for targeted product offers', reqOptions) + this._sendRequest(reqOptions, callback) +} /** * A private function to send a request to the API and parse the response, handling errors as needed */ -CreditOffersClient.prototype._sendRequest = function _sendRequest(reqOptions, callback) { - request(reqOptions, function(err, response, body){ - if(err) { return callback(err); } - if(response.statusCode === 400) { - return processResponseErrors(body, callback); - } else if(response.statusCode === 200) { - debug('Received response', body); - parseResponse(body, callback); +CreditOffersClient.prototype._sendRequest = function _sendRequest (reqOptions, callback) { + request(reqOptions, function (err, response, body) { + if (err) { return callback(err); } + if (response.statusCode === 400) { + return processResponseErrors(body, callback) + } else if (response.statusCode === 200) { + debug('Received response', body) + parseResponse(body, callback) } else { - console.error('Received unexpected status code: ' + response.statusCode); - return callback(new Error('')); + console.error('Received unexpected status code: ' + response.statusCode) + return callback(new Error('')) } - }); + }) - function parseResponse(responseBody, callback) { - if(!responseBody){ - return callback(null, null); + function parseResponse (responseBody, callback) { + if (!responseBody) { + return callback(null, null) } - try{ - var responseObject = JSON.parse(responseBody); - return callback(null, responseObject); + try { + var responseObject = JSON.parse(responseBody) + return callback(null, responseObject) } catch(error) { - return callback(error); + return callback(error) } } - function processResponseErrors(responseBody, callback) { - parseResponse(responseBody, function(err, data){ - if(err) { return callback(err); } + function processResponseErrors (responseBody, callback) { + parseResponse(responseBody, function (err, data) { + if (err) { return callback(err); } - var errorCode = data.code || ''; - var errorDescription = data.description || ''; - var documentationUrl = data.documentationUrl || ''; - var message = format('Received an error from the API: code=%s | description=%s | documentation=%s', errorCode, errorDescription, documentationUrl); - console.error(message); - callback(new Error(message)); - }); + var errorCode = data.code || '' + var errorDescription = data.description || '' + var documentationUrl = data.documentationUrl || '' + var message = format('Received an error from the API: code=%s | description=%s | documentation=%s', errorCode, errorDescription, documentationUrl) + console.error(message) + callback(new Error(message)) + }) } } diff --git a/credit-offers/routes/index.js b/credit-offers/routes/index.js index 88f8aaa..50bc92a 100644 --- a/credit-offers/routes/index.js +++ b/credit-offers/routes/index.js @@ -13,12 +13,12 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ -var express = require('express'); -var router = express.Router(); +var express = require('express') +var router = express.Router() /* GET home page. */ -router.get('/', function(req, res, next) { - res.render('index', { title: 'Credit Offers Sample' }); -}); +router.get('/', function (req, res, next) { + res.render('index', { title: 'Credit Offers Sample' }) +}) -module.exports = router; +module.exports = router diff --git a/credit-offers/routes/offers.js b/credit-offers/routes/offers.js index a4f6120..8debbba 100644 --- a/credit-offers/routes/offers.js +++ b/credit-offers/routes/offers.js @@ -15,29 +15,29 @@ See the License for the specific language governing permissions and limitations /* Defines routes related to finding and displaying credit offers */ -var express = require('express'); -var _ = require('lodash'); -var CreditOffersClient = require('../creditOffersClient'); +var express = require('express') +var _ = require('lodash') +var CreditOffersClient = require('../creditOffersClient') -module.exports = function(options){ - var router = express.Router(); - var client = new CreditOffersClient(options); +module.exports = function (options) { + var router = express.Router() + var client = new CreditOffersClient(options) // POST customer info to check for offers - router.post('/', function(req, res, next){ - var customerInfo = req.body; - client.getTargetedProductsOffer(customerInfo, function(err, response){ - if(err) { return next(err); } + router.post('/', function (req, res, next) { + var customerInfo = req.body + client.getTargetedProductsOffer(customerInfo, function (err, response) { + if (err) { return next(err); } var viewModel = { title: 'Credit Offers', isPrequalified: response.isPrequalified, products: response.products && _.sortBy(response.products, 'priority') - }; + } - res.render('offers', viewModel); - }); - }); + res.render('offers', viewModel) + }) + }) - return router; -}; + return router +} diff --git a/credit-offers_mock_api/app.js b/credit-offers_mock_api/app.js index e4f670c..b8a6210 100644 --- a/credit-offers_mock_api/app.js +++ b/credit-offers_mock_api/app.js @@ -1,57 +1,56 @@ -var express = require('express'); -var path = require('path'); -var logger = require('morgan'); -var cookieParser = require('cookie-parser'); -var bodyParser = require('body-parser'); +var express = require('express') +var path = require('path') +var logger = require('morgan') +var cookieParser = require('cookie-parser') +var bodyParser = require('body-parser') -var routes = require('./routes/index'); -var oauth = require('./routes/oauth'); +var routes = require('./routes/index') +var oauth = require('./routes/oauth') -var app = express(); +var app = express() // view engine setup -app.set('views', path.join(__dirname, 'views')); -app.set('view engine', 'jade'); +app.set('views', path.join(__dirname, 'views')) +app.set('view engine', 'jade') -app.use(logger('dev')); -app.use(bodyParser.json()); -app.use(bodyParser.urlencoded({ extended: false })); -app.use(cookieParser()); -app.use(express.static(path.join(__dirname, 'public'))); +app.use(logger('dev')) +app.use(bodyParser.json()) +app.use(bodyParser.urlencoded({ extended: false })) +app.use(cookieParser()) +app.use(express.static(path.join(__dirname, 'public'))) -app.use('/', routes); -app.use('/oauth/', oauth); +app.use('/', routes) +app.use('/oauth/', oauth) // catch 404 and forward to error handler -app.use(function(req, res, next) { - var err = new Error('Not Found'); - err.status = 404; - next(err); -}); +app.use(function (req, res, next) { + var err = new Error('Not Found') + err.status = 404 + next(err) +}) // error handlers // development error handler // will print stacktrace if (app.get('env') === 'development') { - app.use(function(err, req, res, next) { - res.status(err.status || 500); + app.use(function (err, req, res, next) { + res.status(err.status || 500) res.render('error', { message: err.message, error: err - }); - }); + }) + }) } // production error handler // no stacktraces leaked to user -app.use(function(err, req, res, next) { - res.status(err.status || 500); +app.use(function (err, req, res, next) { + res.status(err.status || 500) res.render('error', { message: err.message, error: {} - }); -}); + }) +}) - -module.exports = app; +module.exports = app diff --git a/credit-offers_mock_api/bin/www b/credit-offers_mock_api/bin/www index c278958..ec0dad6 100644 --- a/credit-offers_mock_api/bin/www +++ b/credit-offers_mock_api/bin/www @@ -4,76 +4,76 @@ * Module dependencies. */ -var app = require('../app'); -var debug = require('debug')('credit-offers_mock_api:server'); -var http = require('http'); +var app = require('../app') +var debug = require('debug')('credit-offers_mock_api:server') +var http = require('http') /** * Get port from environment and store in Express. */ -var port = normalizePort(process.env.PORT || '3002'); -app.set('port', port); +var port = normalizePort(process.env.PORT || '3002') +app.set('port', port) /** * Create HTTP server. */ -var server = http.createServer(app); +var server = http.createServer(app) /** * Listen on provided port, on all network interfaces. */ -server.listen(port); -server.on('error', onError); -server.on('listening', onListening); +server.listen(port) +server.on('error', onError) +server.on('listening', onListening) /** * Normalize a port into a number, string, or false. */ -function normalizePort(val) { - var port = parseInt(val, 10); +function normalizePort (val) { + var port = parseInt(val, 10) if (isNaN(port)) { // named pipe - return val; + return val } if (port >= 0) { // port number - return port; + return port } - return false; + return false } /** * Event listener for HTTP server "error" event. */ -function onError(error) { +function onError (error) { if (error.syscall !== 'listen') { - throw error; + throw error } var bind = typeof port === 'string' ? 'Pipe ' + port - : 'Port ' + port; + : 'Port ' + port // handle specific listen errors with friendly messages switch (error.code) { case 'EACCES': - console.error(bind + ' requires elevated privileges'); - process.exit(1); - break; + console.error(bind + ' requires elevated privileges') + process.exit(1) + break case 'EADDRINUSE': - console.error(bind + ' is already in use'); - process.exit(1); - break; + console.error(bind + ' is already in use') + process.exit(1) + break default: - throw error; + throw error } } @@ -81,10 +81,10 @@ function onError(error) { * Event listener for HTTP server "listening" event. */ -function onListening() { - var addr = server.address(); +function onListening () { + var addr = server.address() var bind = typeof addr === 'string' ? 'pipe ' + addr - : 'port ' + addr.port; - debug('Listening on ' + bind); + : 'port ' + addr.port + debug('Listening on ' + bind) } diff --git a/credit-offers_mock_api/routes/index.js b/credit-offers_mock_api/routes/index.js index d1f8d1e..0d056c1 100644 --- a/credit-offers_mock_api/routes/index.js +++ b/credit-offers_mock_api/routes/index.js @@ -1,5 +1,5 @@ -var express = require('express'); -var router = express.Router(); +var express = require('express') +var router = express.Router() var fakeProducts = { 'good_credit_1': { @@ -107,35 +107,34 @@ var fakeProducts = { alternateText: 'Bronze Preferred Card' } } -}; - +} /* GET home page. */ -router.get('/credit-cards/targeted-products-offer', function(req, res, next) { - console.info(req.query); - var creditRating = req.query.selfAssessedCreditRating; - var response = {}; - var status = 200; - console.info('Determining response based on credit rating (%s)', creditRating); - switch(creditRating) { +router.get('/credit-cards/targeted-products-offer', function (req, res, next) { + console.info(req.query) + var creditRating = req.query.selfAssessedCreditRating + var response = {} + var status = 200 + console.info('Determining response based on credit rating (%s)', creditRating) + switch (creditRating) { case 'Excellent': response = { isPrequalified: true, products: [ fakeProducts['good_credit_1'], fakeProducts['good_credit_2'] ] } - break; + break case 'Average': response = { isPrequalified: true, products: [ fakeProducts['average_credit_1'], fakeProducts['average_credit_2'] ] } - break; + break case 'Rebuilding': response = { isPrequalified: false, products: [ fakeProducts['bad_credit'] ] } - break; + break case null: case undefined: case '': @@ -143,16 +142,17 @@ router.get('/credit-cards/targeted-products-offer', function(req, res, next) { isPrequalified: false, products: [] } + break default: - status = 400; + status = 400 response = { code: 999, description: 'Unknown credit rating: ' + creditRating } - break; + break } - res.status(status); - res.json(response); -}); + res.status(status) + res.json(response) +}) -module.exports = router; +module.exports = router diff --git a/credit-offers_mock_api/routes/oauth.js b/credit-offers_mock_api/routes/oauth.js index 6e2fc7b..4304da3 100644 --- a/credit-offers_mock_api/routes/oauth.js +++ b/credit-offers_mock_api/routes/oauth.js @@ -13,34 +13,32 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - -var express = require('express'); -var router = express.Router(); -var request = require('request'); -var url = require('url'); - -router.get('/auz/authorize', function(req, res, next) { - var clientId = req.query.client_id; - var redirectURI = url.parse(req.query.redirect_uri); - var scope = req.query.scope; - var responseType = req.query.response_type; - - redirectURI.query = { 'authorizationCode': '123456' }; - res.render('authorize', { redirectURI: url.format(redirectURI) }); - //res.redirect(301, url.format(redirectURI)); -}); - -router.post('/auz/authorize', function(req, res, next) { - var redirectURI = req.body.redirect_uri; - res.redirect(301, redirectURI); -}); - -router.post('/oauth20/token', function(req, res, next) { - var authorizationCode = req.body.code; - var clientId = req.body.client_id; - var clientSecret = req.body.client_secret; - var redirectURI = url.parse(req.body.redirect_uri); - var grantType = req.body.grant_type; +var express = require('express') +var router = express.Router() +var url = require('url') + +router.get('/auz/authorize', function (req, res, next) { + var clientId = req.query.client_id + var redirectURI = url.parse(req.query.redirect_uri) + var scope = req.query.scope + var responseType = req.query.response_type + + redirectURI.query = { 'authorizationCode': '123456' } + res.render('authorize', { redirectURI: url.format(redirectURI) }) +// res.redirect(301, url.format(redirectURI)) +}) + +router.post('/auz/authorize', function (req, res, next) { + var redirectURI = req.body.redirect_uri + res.redirect(301, redirectURI) +}) + +router.post('/oauth20/token', function (req, res, next) { + var authorizationCode = req.body.code + var clientId = req.body.client_id + var clientSecret = req.body.client_secret + var redirectURI = url.parse(req.body.redirect_uri) + var grantType = req.body.grant_type var responseBody = { access_token: '5354e3a56036056cffb5a99f368a31cef3aee2a8', @@ -49,7 +47,7 @@ router.post('/oauth20/token', function(req, res, next) { refresh_token: 'cV6tIa3UQncpzGgXfufRwZJvVbwZeoQPpsx7YzxdYNY', id_token: 'eyJraWQiOiIxNDM4NzA2MDM4NTc4IiwiYWxnIjoiUlMyNTYifQ' } - res.json(responseBody); -}); + res.json(responseBody) +}) -module.exports = router; +module.exports = router From 6016204f4c44b673da7687d5f10e68d978b5d49f Mon Sep 17 00:00:00 2001 From: Lorinda Brandon Date: Mon, 22 Feb 2016 17:02:04 -0700 Subject: [PATCH 003/147] Update LICENSE.md --- LICENSE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE.md b/LICENSE.md index 8dada3e..6f75635 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright {yyyy} {name of copyright owner} + Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. From 2c47cdd8f20df58044eeee357afdf692c7d8e1a4 Mon Sep 17 00:00:00 2001 From: Lorinda Brandon Date: Mon, 22 Feb 2016 17:13:22 -0700 Subject: [PATCH 004/147] Update README.md --- README.md | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index db7e4c5..a88d5db 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,13 @@ -# Credit Offers API Reference App +# Credit Offers API Reference Application Code -*Summary of Credit Offers goes here.* +Credit Offers is a card acquisition service that provides prequalified credit offer listings based on end customer provided information. Affiliates are able to provide these offers directly without need of a web redirect. If a prequalified offer is not available, a default offer is returned by us. -This reference app illustrates the use of the Credit Offers API to collect customer information and retrieve a list of targeted product offers for display. +## Software Requirements Including version +This is version 1.0 of the Credit Offers API Reference Application Code. For software requirements, see Build/Install Instructions below. -## Getting Started +This reference app illustrates the use of the Credit Offers API to collect customer information and retrieve a list of targeted product offers for display. If you encounter any issues using this reference code, please submit them in the form of GitHub issues. + +## Build/Install Instructions ### config.js You can configure your clientID and clientSecret in credit-offers/config.js. In addition, if you change the default port for the mock API, you also need to update this file. @@ -30,3 +33,11 @@ This will submit a request to the Credit Offers endpoint and redirect the user t ### Viewing more details To get a deeper look at the messages being passed, start the app with the following command `DEBUG=credit-offers:* NODE_DEBUG=request npm start`. This will activate detailed debug logging to the console, showing the details of the request to the API and the response received. + +## Architecture + +## Roadmap +This reference app code is intended as a starting place for developers who want to use the Credit Offers API. As such, it will be updated with new functionality only when the Credit Offers API is updated with new functionality. + +## Contribution Guidelines +We encourage any contributions that align with the intent of this project and add more functionality or languages that other developers can make use of. To contribute to the project, please submit a PR for our review. Before contributing any source code, familiarize yourself with the Apache License 2.0 (license.md), which controls the licensing for this project. From 1497be2b3081fc297974cc3b65abb7d482013011 Mon Sep 17 00:00:00 2001 From: rkorsak Date: Tue, 23 Feb 2016 08:44:01 -0500 Subject: [PATCH 005/147] Update README with dependency info --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index a88d5db..3921f06 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,11 @@ This is version 1.0 of the Credit Offers API Reference Application Code. For sof This reference app illustrates the use of the Credit Offers API to collect customer information and retrieve a list of targeted product offers for display. If you encounter any issues using this reference code, please submit them in the form of GitHub issues. ## Build/Install Instructions +### Dependencies +* [Node.js](https://nodejs.org) 4.X or higher + +All other dependencies are loaded with [npm](https://www.npmjs.com/). All dependencies are cross-platform. Notable dependencies are listed below. +* [express](http://expressjs.com/) - Minimalist web framework for Node.js ### config.js You can configure your clientID and clientSecret in credit-offers/config.js. In addition, if you change the default port for the mock API, you also need to update this file. From d7bbfd2b4a0496db3ebb105716c77bb2d8874910 Mon Sep 17 00:00:00 2001 From: rkorsak Date: Tue, 23 Feb 2016 09:12:25 -0500 Subject: [PATCH 006/147] Update CreditOffersClient with style tweaks and proper error reporting for unknown status codes --- credit-offers/creditOffersClient.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/credit-offers/creditOffersClient.js b/credit-offers/creditOffersClient.js index 3188ae1..8ca2a2f 100644 --- a/credit-offers/creditOffersClient.js +++ b/credit-offers/creditOffersClient.js @@ -14,7 +14,6 @@ See the License for the specific language governing permissions and limitations */ var request = require('request') -var qs = require('querystring') var _ = require('lodash') var format = require('util').format var debug = require('debug')('credit-offers:api-client') @@ -65,15 +64,16 @@ CreditOffersClient.prototype.getTargetedProductsOffer = function getTargetedProd */ CreditOffersClient.prototype._sendRequest = function _sendRequest (reqOptions, callback) { request(reqOptions, function (err, response, body) { - if (err) { return callback(err); } + if (err) { return callback(err) } if (response.statusCode === 400) { return processResponseErrors(body, callback) } else if (response.statusCode === 200) { debug('Received response', body) parseResponse(body, callback) } else { - console.error('Received unexpected status code: ' + response.statusCode) - return callback(new Error('')) + var errorMessage = 'Received unexpected status code: ' + response.statusCode + console.error(errorMessage) + return callback(new Error(errorMessage)) } }) @@ -85,14 +85,14 @@ CreditOffersClient.prototype._sendRequest = function _sendRequest (reqOptions, c try { var responseObject = JSON.parse(responseBody) return callback(null, responseObject) - } catch(error) { + } catch (error) { return callback(error) } } function processResponseErrors (responseBody, callback) { parseResponse(responseBody, function (err, data) { - if (err) { return callback(err); } + if (err) { return callback(err) } var errorCode = data.code || '' var errorDescription = data.description || '' From 5af16a7ffa1041eda75cc01413c8bf1f9c3c9421 Mon Sep 17 00:00:00 2001 From: rkorsak Date: Tue, 23 Feb 2016 09:36:35 -0500 Subject: [PATCH 007/147] Added content to the architecture section of the README --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 3921f06..96ba5a5 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,11 @@ This will submit a request to the Credit Offers endpoint and redirect the user t To get a deeper look at the messages being passed, start the app with the following command `DEBUG=credit-offers:* NODE_DEBUG=request npm start`. This will activate detailed debug logging to the console, showing the details of the request to the API and the response received. ## Architecture +This is a [Node.js](https://nodejs.org) 4.x and higher app built with [Express](http://expressjs.com/) 4.13.1. Because of the simple nature, there is no session management or data persistence. + +The Node.js https library is verbose and repetitive for our narrow use case, so we also used [request](https://github.com/request/request) for calls to the Credit Offers API. + +The application structure follows the pattern generated by the [Express application generator](http://expressjs.com/en/starter/generator.html). ## Roadmap This reference app code is intended as a starting place for developers who want to use the Credit Offers API. As such, it will be updated with new functionality only when the Credit Offers API is updated with new functionality. From a1cb7387d7cdf43e4b9557be9461942352e44c82 Mon Sep 17 00:00:00 2001 From: Brian Meeker Date: Wed, 24 Feb 2016 09:47:03 -0500 Subject: [PATCH 008/147] Added new images and json for credit offers. * Each offer has multiple images. We just pull off the first one. --- credit-offers/views/offers.jade | 4 +- ...eric-VentureOne-EMV-flat-244x154-06-15.png | Bin 0 -> 23386 bytes ...ericEMV-Venture-EMV-flat-244x154-06-15.png | Bin 0 -> 21775 bytes ...0-MC-Blue-Steel-EMV-flat-244x154-06-15.png | Bin 0 -> 16768 bytes .../public/images/chicken_thumbnail.jpg | Bin 18121 -> 0 bytes .../public/images/dog_nose_thumbnail.jpg | Bin 15089 -> 0 bytes .../public/images/hammock_thumbnail.jpg | Bin 16420 -> 0 bytes .../public/images/steps_thumbnail.jpg | Bin 15551 -> 0 bytes .../public/images/water_huts_thumbnail.jpg | Bin 8060 -> 0 bytes .../images/www-venture-visa-sig-flat-9-14.png | Bin 0 -> 21550 bytes credit-offers_mock_api/routes/index.js | 173 ++++++++---------- 11 files changed, 80 insertions(+), 97 deletions(-) create mode 100644 credit-offers_mock_api/public/images/JB16760-Generic-VentureOne-EMV-flat-244x154-06-15.png create mode 100644 credit-offers_mock_api/public/images/JB16760-GenericEMV-Venture-EMV-flat-244x154-06-15.png create mode 100644 credit-offers_mock_api/public/images/JB16760-MC-Blue-Steel-EMV-flat-244x154-06-15.png delete mode 100644 credit-offers_mock_api/public/images/chicken_thumbnail.jpg delete mode 100644 credit-offers_mock_api/public/images/dog_nose_thumbnail.jpg delete mode 100644 credit-offers_mock_api/public/images/hammock_thumbnail.jpg delete mode 100644 credit-offers_mock_api/public/images/steps_thumbnail.jpg delete mode 100644 credit-offers_mock_api/public/images/water_huts_thumbnail.jpg create mode 100644 credit-offers_mock_api/public/images/www-venture-visa-sig-flat-9-14.png diff --git a/credit-offers/views/offers.jade b/credit-offers/views/offers.jade index 60a9b3e..a2f91b9 100644 --- a/credit-offers/views/offers.jade +++ b/credit-offers/views/offers.jade @@ -30,8 +30,8 @@ block content div.product-offer.media div.media-left img.product-image( - src = product.image.url.href - alt = product.image.alternateText + src = product.images[0].url.href + alt = product.images[0].alternateText ) div.media-body h4.media-heading= product.name diff --git a/credit-offers_mock_api/public/images/JB16760-Generic-VentureOne-EMV-flat-244x154-06-15.png b/credit-offers_mock_api/public/images/JB16760-Generic-VentureOne-EMV-flat-244x154-06-15.png new file mode 100644 index 0000000000000000000000000000000000000000..0e18f499a35f9cad888f9608b3dd1804b6fbfb34 GIT binary patch literal 23386 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 literal 0 HcmV?d00001 diff --git a/credit-offers_mock_api/public/images/chicken_thumbnail.jpg b/credit-offers_mock_api/public/images/chicken_thumbnail.jpg deleted file mode 100644 index 0ae97c7b99c8ea74cf1b695ed29fd87fb6932a72..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18121 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

!|t?b4@PW5%e36 zKCz;>A3RtKPlw~<%f@Id?y(MQ=lGs`hb>B@&$Z4jinINY{|p_AD25)1hQFT*o&Obx zkZE<}U{SQ0>Zv?&GhivQUu>qdvm*VDKuTG`wU_Zk_i{M!+6YXib6vMW9yyg$1Y~(q z7;h`?95=qDcq`P?io-UV(&^G$#Y)KZ9i#NGVG<589phO7n%(LE+bq-^7I; zRZOiK)&S$Ye6utu?2B>w+0;i?)yK8dZdv&gOD{<0FXb(&ZniFc(CQT~;CJz|nNkta zlT54@W^(C#_BC{xpt$}HGrF!U7OW+(o$l!~Gs&a66CAXGD2@)}P?k4!r;Ff{DA|X+ z=u5p#v`M)iqjia&(D&$M?ucSH8Y;~3uyoVwY#rt%76WSGjxi4(yjh0-46|_5TvGe` zB9l!?njOBmo%N%%{7k^$Kz;gdow)7IsECeCX{V>f(=R#K^E$`dYusleOm5!!9&HrS|%CgJT0H9+yPhCF)ix;%PK@xnwg zS`V@2tH#o=VLk|UIPbZSpX^Q5H@shp(*K&Gu_-(L6dfwWYZ8o_S9a#EoACHtH^H=> zEsyQ&sQ^p4HHHIL2D5bC=?`Tn+2-0yBhi})n;u%56+WZFG?<{pLTy1OLF41^%SR?j z(xc!ZGHhp|mFfi-bBqHvrA~?^%}=o{v_I^!Cf_xlOXr*TV_F)S)^-T!`9r1|TIt-@ z%#%(0@mF0#^-~1Q5{8VY+belDN5;`qbI~ajt*2w{f3BEn(L9Z=9Sf9;co{swy6s~A zVTo_z_|wr{ow_1S0$E;(-?)R2ncVO&{KB#BCvrELLJ~ACCu>YRMfR3?R*mt66t+^& z@`nrcl99Z$4&&!*?qYRDz1WT0qq{jwfi=jL5RvRu4C1c1>^6L(Uig`*7#a~APS_;P z9lFhmmI{qL&CD8e4^=s{mXBqMqZrH-IF}S#lqmRhHifuck>T!+o@F*l+a=rlR2dWF zcd6)FCW_a$YZ=*-3xA%u7=*HR_40nFj`Y5NzFuW2Lm1;cKp}l2U@@&T_x2vw*RjNl z|vGgwvvW~~KrxiuN%_XCzIR;IP ziVHVY$A;v2tTA2XT6k}_FLpF#@!Wr{_=sT{9Y7H@9hxba-~isuaSHay$6e~qJQ*}! ztJE@KWw`I_=QB3wW{nB>*=pHZyiDI^B4QdTq*`4~!QL5j(R0$0E;DO*mZ509G~<5D zw1&%x2;sE|J(6c_-d2Io7dxEZ?&Pg%TeQsNUEIhUXak(V60o~(vny!{_L zxMb$ zA1qZ36{7EN*$OdP{+pLfrJO8wU{+EW8!5*idPE{UiA-~vm%~07S%S;xN brllja#X)z!G23(6NRa34s!3kve*b>~u#s-r diff --git a/credit-offers_mock_api/public/images/dog_nose_thumbnail.jpg b/credit-offers_mock_api/public/images/dog_nose_thumbnail.jpg deleted file mode 100644 index 6b809c1f6ba8298d520b87f8c636bbe5b9fe7afc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15089 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�cA+N|6QU{IbA21?l%%86NpM6^UDeTGWxb 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 diff --git a/credit-offers_mock_api/public/images/water_huts_thumbnail.jpg b/credit-offers_mock_api/public/images/water_huts_thumbnail.jpg deleted file mode 100644 index 4907072c287b36aef8367fb2da5ac10149a58a65..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8060 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 L!m(&Se(e7NA0x<- diff --git a/credit-offers_mock_api/public/images/www-venture-visa-sig-flat-9-14.png b/credit-offers_mock_api/public/images/www-venture-visa-sig-flat-9-14.png new file mode 100644 index 0000000000000000000000000000000000000000..d03e705c319bda101b196bd68bd08b02d37576c0 GIT binary patch literal 21550 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

{4sp3#$8Y0 z(|1_-c3B5O*X6+!tx)60k84djVFr%*$Fc60qk_@T%iN2Zj{)VL`SX-`6e84rB}UtS zyc6K{hmLp?bZ)Dvhc6%V?oGi9OyNyqMc7IuJd|T6`IdAPEu+ZFGCnw0o~mz$Jn2hl zR&jPqJWuRk4gOeuC@Ej1+GeqG-0u`7#SwD&p4~e|c^CTHf3TmYSJ?V@)L{_E(RF8?8J50)=!;0d-J}gT^ipuP0bJlXw@dF z{5b`oOY4WHB>`GQEVqTsn{#&1DIm{p8>3g$Wrt&QEGL)U-K(ndrz=o$G}`>r+_bY-d&c)16MD?jv?K*6Z@a3e1m&Vwn(lt_J&k|lX!uJFs2)&+mRe+sKnCP|`)t=0cZ%*>H+^IjSL1U%u zvQK5Rtst=Ly=xqdJSO?_stIk3o^aY6vSKYh0+dU!rY3Mn3)rjP=`7ei-I&*M?|ip> zou+nhp=;gLb@Y&@WLdwMWn)kx`P3wzBCx+jYncC9xMsH6KTic7=7nlyOWL&v*xmp) z9j|Aa8X=gT<=v6l^WOBkId`=18obC?Nq--6Sszu;TeIu@(3hQaHE{`MEX%rHaKRj! zOXdbJ4-`4%FwFPx{R+b5&@RW?jjv^U9j2hZ4&%~?t9AIA-q^CdTMT8MyxyGVqVnzk z{?8z5R$v3!K-+DLRJx?+|Cd{;Ev&jppxVMLd~+<|m3_i;l{L7yN~h4;YVnOITBY!o zSxV`vV+*q&%=j@lD#g$rh6h9@=6y_~4>DMx7CS1lz#A!@6$6>Sp{LebtjAY0y?yB3 z>-TUt`es~AO-TDV#5gA$8xSR`w=1KNT!~7Am0+8T1#mXEXVS)6vGAPT(ytZlqAi9N zvZ3)jHZ_Yw)>_Dn)49TxKB>(4`cZjAtR}fiU#iS|j5hJ6Rb_ju90sccuo_L-vkZ5K zstcd!D7i6kHI}*;-2EY!9v9&%zyTj$YM>unP`dopPH!}r;9~K9E|VynToxQW8PKz$ zbWhQmkdwSvIL%6-)gLT;#8C74Yq3bfY_tH2I<>UWGTznq2G2yqp>RsX%QmkUG17X`C^Y&EB@devf3!> zwE+Ya`|HriEv-uW2yPz?T`){_(GFpqOhNF%a83KS%YIy)zxyei!~g*$kR?@9X5>7d z5o!L2Tf$vOLJnd+DF89ZY+@oU-#6tAF<00vwr>pJ?ObyQv};tvy!*}0L#*>i>>I`8W# zEW`K2Tw6IlM;ESqcqAdZhKszX*Zs`b#`RDwfE&zQ2Am}_)t&{;&CC4pzayxuvRaCz zVeV`y#Q3~5SZHdLa!heu$#BKW0ExslYMSr1_d4eDotC0a-S%7Z1?L_jg4AZO(%2ku zrxrwqfSq$XWN&xwa9AQkNT=Do#dSXnoJ&u??T4|DX;}7UUbxN|1eNU&V6X6I>uFpE zR}k zwpGci#s~uyklRXES>B%axbYjS+S>N*VbX$k_>-x#*;N|`qOiK_4mDDmPz>P;$1;v$ zE(j}v(hcwbQ2oxhY3nGNL`9X(9nb${a#ys72&*iiy=}u?+JLH#ALZRQ=_h7b?^>I3 zj*-@BRbMH^u&IaR$04R~uT$~lr-^P)M}PJV=s~SvfRDJEN_~*i1ME5&Q>A0!uADXw zbHh$H`&8dgTt9{KH)K*{+Olk?3;H0=A+u$txb*D;r$cHxZS$UA^f@FOQvoYGNw^Gi zU_0NV@CJ(~%onLdgHL`@8nFTJ)+UgMGD5aJWse6KkU? z;l2-ZTzrePjwvY-EsGVGX_R3eTNDh17sXBvr1qlXMFnEaknEQ0xG|@rPtQ=;T*0BhfLTd3OLf?b)HQ%T`Z;8I&C7%~UMBOm;Nu+Hnup)!5L?KC zDBnA4taei%8iZ39Q3TqYOy^{lY=V)-u7No(?XJW@(-VDa-r)jgOOUrYmj<8(`Ax@7 z&_M3%;|JcBYE8sQR$%*7XC8c zt0Igf4>9juoXb49(>(Cs^6Y;OJ{5#F4oxYWl$6lJvwZw~Z3rTOnqc{PS$ zagbVg(MGmu&7uv$#zW~m<4EPa{#tWpckWeQ^#QlF6BCH7+svu~|l z+{a+Cqn=t^6yT=wFtVSDNR@;|{0;|*Mb}PVGuG+ap zbWB`(YLWh1fu(-fwKN&-m;K|lZxT_I{4IqLXEL?`u5j1%mQ~h(2vQYM&tMgMwM>>_ z^D4wT>t{i#^<8m7zzY4tVRinKm<3%%d5t}}0J!Y?F{vV<7MBWK#fNc5Pe+i$TX4_t z+=3>qTAn{hP5o?*#ad8Q_1LB~yi4>cztiALVn_tBk`I>cX%sM7W>3HYiuyWM$Wqwc zS-O^*gh6G8|Nns>o4XEfmSQIz_49Kq@plcIcmVL%IAMAu$TMICY0Rppv3&Qh;)IOz z6oWwaxFm9|dSTSRT4{zm> z5j&|aZfZfXR#4$In60PDI;us?-QqNIfH2`ysA({kS(36AqpLF_BD17V>_lPfK;^}4 z%TJ5Ds<_}sd*F~Cupt#dz-hQ(lP4>a>(2n;7!hEevy2jEnX7}`oci--RFo_E4!OM@ z{itE%_Jj~@X7KJHFqsUO)n8z5QwvQy%1RtZx&k0T*Vl`+yYR2Eq=Rf9?Y#No;aMmpQ4uFRGg4V zcsmKP!!gq#VvuSoAm#;v3r&G=ANnfRA@7BdAIa6qlrp)HT145EjEN!8&px_~e6juZfsXs`m)eSrzYvLN7=G1|F-b_lXBN$vt#k34aoTw`pi~MrY zLKBeD(weGvogdo9rHrk8dX&}xtM&yE^NE@(wBJhN{-+aa$V5BeRCLoVC3 zsB3XBbtOf_+9ma%IoWub9T8B5Sm+X%P*|)mu+zW+9t+&Sm5ZHOqSmArL|DBoM`4A_ z_5U7h3<8v^MoXX+xQ{52Q_K?NWm#lDjv{YYr8j%1YttRd+N9oTnVO}!-)vT%&DAWs zqd8QWv4`^9U9um8>|)MUwl$TvntZPh`zzBh^UIK!6P96W{52$2Kh{K zm}^d9Yf;o%A=hIhVjMU{{pjD4%Lh4y!1$ey%_P<35}(V_n;WORl*oL9#GHzk-wJp_ z^G+Q5r0>$aO2e+{AHBRdrt!ObzO?3S?rD9R@A7$m<~)5fjVb1NeJO{-(q`som79~d zZp#<;U@JR++SSjWEX^1St01c!gLx*PSpbw9T3trE-r5NA%0i$nfytpxW)P_M@-&i= zBT!SU#+bG>Z4UUTXazL@<;JyQf>eKFmgHa%VRoT}nk8XrKMA8)($Ha=7hcbPuL|2` zMPYAgo^62V&7k)6S$ncoYQ7h>F~#xH*s`umQ}(Xrq=2jTSea8@n61n`oqa`cVdV)Y zRkZ;J?Ykiq!boKSutqs6nE>DTWBqSs;Ft#%0%Im&loK&km+)fRo79}gcE`l zv*h;Gb3kQqk_&AaG?37cNefN?%#xpwndTd(`RRDI4I*gDU&bFoXfSNNSG74#*5;6( zTT@lp7gKy`Yg+-_G`+bge(#RgwhHGpFaNq+%H8sE^;LQ~2ijLm!VSzWs3m>e4 zxh?6GwLLLT&Zizfcv^aj5oC}<`$sRe<7tVQ9EUTyJwlyz0yL1F3RA!m`9?TWVU!xC z$+lIN<(Bm;HePx%JEq`ye_A`YR;Pg|3fCKo3Tm(<)z%9q14?L++6R>*q7N#Ff_R~d z|NnRReQQ%i!8a!-SradC7JFva>^*xj&z2v)y#M~;>ysa^-@JSL=IxJ9FJ68>d}!^{ z;X7X?`uKxg@?Sr{c=_YwTVB7uUtW9h=32ilrzx`JNX@=mh^{serYB?N$P#^6K`L32 z@NFF?*A*)*0LIFascUhXOr?%2DaP5aCum$jOJ{0Xo@PkDT5= zWFDQlCY?BxHvm=aie-0q_sX$x`KXh8^Zn0Xa(p6LPw%QBI&QH69>h4?F_ThVMLp7Y z2xW=J!V%&coGlru%Ujwea0P5f4woG`VRjsSOj`~S;@lQ|W#&a~Ls#d#&o)0>K0AHd z+S7rk<+OYLs*U^m&xT$1tPM|3PxpKSbbGoR;zn5dYSxb0GS^>}xgkB56Jg%%lIl|H zYZ5P~35UsrROmYA3U6;F(;AzSjXuVF1nbqdAuMa4)?U0}8|F1iD@$XKhq%RTs1)Fv zYiKhsW?W7IJgULB1}yb9AY)8Kk6BkMt^`kENFqeTwAwkw6*g3s@GM#CV&~5Nr}~DK zb&7cbxSH2wA@EZlT0@K?PJmXHrV%dZBAIvGs?qvZ1*h)-b}%Xlt$(q*l64W*EC_)jeZopbu@}L`-J<^tb-4!TVsxF*P zT~T6-`h}m=nvyHph|SmMw>RT#V0`9o=1pJ;Lzp=6U=y-lHxMAIYDc= z$7_NdZnR))H)%~>5@^(5k1W+J6RfS(IS}M-`f(1|gm?rhvSix475Xj}vjR$;9@cOT z^IJZ7c#w5@O%?V5sb{}Wh+B8zsrI2}l%AC(U>16O!v@G^7-DIVAD|Zu1?eyY<|RTD zZt@=a!Nh~?cyO(FC`*0Z;}Bgzvp!M{AWME)A9gir0SeRe4x(M+9?Ft*BTG6>4q8NP z@th!ur73h_c;<&&XHj+%4NZ9dB}>f>9lE;%RE1$1Xo6U4;uN95&t4oD#`>#D#H84! zd!V;SR&h#D&%7-;Lm93%>wK46Sz^EJjE1(CSoMl)Lg1qsO!lOAknN^5GC| z?o09_WG;1?XZ*sx`<$NOJd>G7r36UWc3qrL;wmO7v51GQhK??oF>mpiBH{QE`C(xK z%8_GG18Z&br6;{ZeoIS>EVV(X?CC8emiBJUNnbB3^Y0--EE|BkZw57i5r`~}D>+XL zTd-|Q;3!K5emeUytvS&EsJbarSqd2aum_*qf1ZhDR~+Gd1IW3?yu(a0Ygbc^4a)-{ zgLkdJM4hN$U@%)F=)+g-w(|-#vTI^#XxMjqfus~=iJy7hMw&db-xhE^KY^epVm_Vmco+>d~wZ5pb+mc>bTsncX)^;o&oOUJvYQY|$B34TPE@F;B2 zJ8lnKIA*WQV7$q?Zk19 z8L)H__Y7E?mj{+Eb@GBRHQRLjh~p?7aK5G2baWuFwWC#nx3B~-GDK^}Rbgrh9`WT1 ziwhUd^P5L>ONfMdDs=*2m=xl$6U_aEWfqwgdr#mW-uBtkX?qTUp3tBDiYQkK45yddE# zXWS@Fvo5t^;*m*6qw#LzHb#~j;03@aU}?=~A87F)%K8$m@!DGEnK5&;iOV3SZWDXB zFtdE!i7MSP@=4lr=NIdroo*bb$-UK0gnb#fw!ScqF`dphc1PN2%?~Snd6Xp@ zvwV>yj7+}PRk=zSBVWFH{_L5v{BU35`5U+js&WMHyamprV)-YW2YBQ Date: Wed, 24 Feb 2016 10:30:43 -0500 Subject: [PATCH 009/147] Add 2-legged OAuth support to Credit Offers --- credit-offers/config.js | 19 +++++-- credit-offers/creditOffersClient.js | 36 ++++++++----- credit-offers/oauth/index.js | 72 ++++++++++++++++++++++++++ credit-offers/routes/offers.js | 5 +- credit-offers_mock_api/package.json | 1 + credit-offers_mock_api/routes/oauth.js | 33 +++++++++--- 6 files changed, 138 insertions(+), 28 deletions(-) create mode 100644 credit-offers/oauth/index.js diff --git a/credit-offers/config.js b/credit-offers/config.js index 1cd2634..589e980 100644 --- a/credit-offers/config.js +++ b/credit-offers/config.js @@ -13,13 +13,22 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ +var oauthHost = 'http://localhost:3002' +var creditOffersHost = 'http://localhost:3002' + module.exports = { // Settings for connecting to the Credit Offers API creditOffers: { - // Change this to the URL of the environment you are connecting to - url: 'http://localhost:3002', - apiVersion: 1, - clientID: 'COF_CLIENT_ID', - clientSecret: 'COF_CLIENT_SECRET' + client: { + // The URL of the Credit Offers environment you are connecting to. + url: creditOffersHost, + apiVersion: 1 + }, + oauth: { + tokenURL: oauthHost + '/oauth/oauth20/token', + // The clientId and clientSecret you received when registering your app. + clientID: 'COF_CLIENT_ID', + clientSecret: 'COF_CLIENT_SECRET' + } } } diff --git a/credit-offers/creditOffersClient.js b/credit-offers/creditOffersClient.js index 8ca2a2f..edce800 100644 --- a/credit-offers/creditOffersClient.js +++ b/credit-offers/creditOffersClient.js @@ -22,22 +22,22 @@ var debug = require('debug')('credit-offers:api-client') var defaultOptions = { // TODO: update this value with actual/staging api host url: 'https://api.capitalone.com', - apiVersion: 1, - clientID: null, - clientSecret: null + apiVersion: 1 } /** * The API client class * @param options {object} Client options (host url, API version) */ -function CreditOffersClient (options) { +function CreditOffersClient (options, oauth) { if (!this instanceof CreditOffersClient) { return new CreditOffersClient(options) } // Store the supplied options, using default values if not specified this.options = _.defaults({}, options, defaultOptions) + this.oauth = oauth + debug('Initializing Credit Offers client', this.options) } module.exports = CreditOffersClient @@ -46,17 +46,25 @@ module.exports = CreditOffersClient * @param customerInfo {object} Represents the customer info to pass to the API */ CreditOffersClient.prototype.getTargetedProductsOffer = function getTargetedProductsOffer (customerInfo, callback) { - var reqOptions = { - baseUrl: this.options.url, - url: '/credit-cards/targeted-products-offer', - method: 'GET', - qs: customerInfo, - headers: { - 'Accept': 'application/json; v=' + this.options.apiVersion + var client = this + this.oauth.withToken(function (err, token) { + if (err) { return callback(err) } + + var reqOptions = { + baseUrl: client.options.url, + url: '/credit-cards/targeted-products-offer', + method: 'GET', + qs: customerInfo, + headers: { + 'Accept': 'application/json; v=' + client.options.apiVersion + }, + auth: { + bearer: token.access_token + } } - } - debug('Sending request for targeted product offers', reqOptions) - this._sendRequest(reqOptions, callback) + debug('Sending request for targeted product offers', reqOptions) + client._sendRequest(reqOptions, callback) + }) } /** diff --git a/credit-offers/oauth/index.js b/credit-offers/oauth/index.js new file mode 100644 index 0000000..70d3c53 --- /dev/null +++ b/credit-offers/oauth/index.js @@ -0,0 +1,72 @@ +/* +Copyright 2016 Capital One Services, LLC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and limitations under the License. +*/ + +var request = require('request') +var debug = require('debug')('credit-offers:oauth') + +/** + * Provides functions for oauth token management + */ +module.exports = function (options) { + debug('Initializing oauth module', options) + var clientID = options.clientID + var clientSecret = options.clientSecret + var tokenURL = options.tokenURL + + /** + * Get a new access token using client credentials + */ + function withToken (callback) { + debug('Getting a new access token') + var reqOptions = { + url: tokenURL, + method: 'POST', + form: { + 'client_id': clientID, + 'client_secret': clientSecret, + 'grant_type': 'client_credentials' + } + } + + request(reqOptions, function (error, response, body) { + if (error) { + return callback(error) + } + if (response.status >= 400) { + return callback(new Error('OAuth access token exchange failed')) + } + + if (!body) { + var missingTokenError = new Error('OAuth response body did not include an access token') + console.error(missingTokenError) + return callback(missingTokenError) + } + + debug('Received token response', body) + try { + var newToken = JSON.parse(body) + } catch (parseError) { + return callback(parseError) + } + debug('Parsed new token', newToken) + + return callback(null, newToken) + }) + } + + return { + withToken: withToken + } +} diff --git a/credit-offers/routes/offers.js b/credit-offers/routes/offers.js index 8debbba..90b5333 100644 --- a/credit-offers/routes/offers.js +++ b/credit-offers/routes/offers.js @@ -18,16 +18,17 @@ See the License for the specific language governing permissions and limitations var express = require('express') var _ = require('lodash') var CreditOffersClient = require('../creditOffersClient') +var oauth = require('../oauth') module.exports = function (options) { var router = express.Router() - var client = new CreditOffersClient(options) + var client = new CreditOffersClient(options.client, oauth(options.oauth)) // POST customer info to check for offers router.post('/', function (req, res, next) { var customerInfo = req.body client.getTargetedProductsOffer(customerInfo, function (err, response) { - if (err) { return next(err); } + if (err) { return next(err) } var viewModel = { title: 'Credit Offers', diff --git a/credit-offers_mock_api/package.json b/credit-offers_mock_api/package.json index 75eb376..d6c90f9 100644 --- a/credit-offers_mock_api/package.json +++ b/credit-offers_mock_api/package.json @@ -10,6 +10,7 @@ "cookie-parser": "~1.3.5", "debug": "~2.2.0", "express": "~4.13.1", + "jade": "^1.11.0", "morgan": "~1.6.1", "request": "^2.69.0" } diff --git a/credit-offers_mock_api/routes/oauth.js b/credit-offers_mock_api/routes/oauth.js index 4304da3..d0e00b3 100644 --- a/credit-offers_mock_api/routes/oauth.js +++ b/credit-offers_mock_api/routes/oauth.js @@ -17,6 +17,9 @@ var express = require('express') var router = express.Router() var url = require('url') +/** + * Support for the authorization step of 3-legged OAuth (not currently used for Credit Offers) + */ router.get('/auz/authorize', function (req, res, next) { var clientId = req.query.client_id var redirectURI = url.parse(req.query.redirect_uri) @@ -33,20 +36,36 @@ router.post('/auz/authorize', function (req, res, next) { res.redirect(301, redirectURI) }) +/** + * Get a new token + * Supports multiple grant_type options + */ router.post('/oauth20/token', function (req, res, next) { var authorizationCode = req.body.code var clientId = req.body.client_id var clientSecret = req.body.client_secret - var redirectURI = url.parse(req.body.redirect_uri) + var redirectURI = req.body.redirect_uri && url.parse(req.body.redirect_uri) var grantType = req.body.grant_type - var responseBody = { - access_token: '5354e3a56036056cffb5a99f368a31cef3aee2a8', - token_type: 'Bearer', - expires_in: '900', - refresh_token: 'cV6tIa3UQncpzGgXfufRwZJvVbwZeoQPpsx7YzxdYNY', - id_token: 'eyJraWQiOiIxNDM4NzA2MDM4NTc4IiwiYWxnIjoiUlMyNTYifQ' + var responseBody + if (grantType === 'client_credentials') { + responseBody = { + access_token: '5354e3a56036056cffb5a99f368a31cef3aee2a8', + token_type: 'Bearer', + expires_in: '900', + refresh_token: 'cV6tIa3UQncpzGgXfufRwZJvVbwZeoQPpsx7YzxdYNY', + id_token: 'eyJraWQiOiIxNDM4NzA2MDM4NTc4IiwiYWxnIjoiUlMyNTYifQ' + } + } else if (grantType === 'authorization_code' || grantType === 'refresh_token') { + responseBody = { + access_token: '5354e3a56036056cffb5a99f368a31cef3aee2a8', + token_type: 'Bearer', + expires_in: '900' + } + } else { + res.status(400) } + res.json(responseBody) }) From ceaf15a6ae6731d65b0a20990b0f0bc62b4ab8d2 Mon Sep 17 00:00:00 2001 From: Brian Meeker Date: Wed, 24 Feb 2016 10:48:55 -0500 Subject: [PATCH 010/147] Fixed a bad image URL in the mock data. --- credit-offers_mock_api/routes/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/credit-offers_mock_api/routes/index.js b/credit-offers_mock_api/routes/index.js index cf20837..673270a 100644 --- a/credit-offers_mock_api/routes/index.js +++ b/credit-offers_mock_api/routes/index.js @@ -42,7 +42,7 @@ var fakeProducts = { "tier": "EXCELLENT CREDIT", "images": [{ "url": { - "href": "http://images/www-venture-one-visa-sig-flat-9-14.png", + "href": "http://localhost:3002/images/www-venture-visa-sig-flat-9-14.png", "method": "GET", "type": "image/png" }, From 3d8899041dbc41b17a85940d43bb28bfb4761139 Mon Sep 17 00:00:00 2001 From: Brian Meeker Date: Wed, 24 Feb 2016 11:07:41 -0500 Subject: [PATCH 011/147] Replaced config.js with config.js.sample and added config.js to .gitignore. * Users should never put their version of config.js under version control. --- .gitignore | 3 +++ credit-offers/{config.js => config.js.sample} | 5 +++++ 2 files changed, 8 insertions(+) rename credit-offers/{config.js => config.js.sample} (86%) diff --git a/.gitignore b/.gitignore index 1ffcadd..b6d0cc0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +# Ignore config files. Users should alter the sample. +config.js + # Thumbs Thumbs.db diff --git a/credit-offers/config.js b/credit-offers/config.js.sample similarity index 86% rename from credit-offers/config.js rename to credit-offers/config.js.sample index 589e980..bb6c7a2 100644 --- a/credit-offers/config.js +++ b/credit-offers/config.js.sample @@ -13,6 +13,11 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ +/* + * Copy this file to config.js and enter your configuration info. + * config.js should not be under version control since it contains your + * client_id and client_secret. + */ var oauthHost = 'http://localhost:3002' var creditOffersHost = 'http://localhost:3002' From b5d5047b99e74ecc75f79a2d543fef7782bd3970 Mon Sep 17 00:00:00 2001 From: Brian Meeker Date: Wed, 24 Feb 2016 11:11:13 -0500 Subject: [PATCH 012/147] Updated the README with info about config.js.sample. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 96ba5a5..e55c6a8 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ All other dependencies are loaded with [npm](https://www.npmjs.com/). All depend * [express](http://expressjs.com/) - Minimalist web framework for Node.js ### config.js -You can configure your clientID and clientSecret in credit-offers/config.js. In addition, if you change the default port for the mock API, you also need to update this file. +config.js contains information specific to your app, such as your client_id and client_secret. Create this file by copying ![config.js.sample](/credit-offers/config.js.sample). Be careful not to put config.js into version control. ### Start the mock API From the project root: From 178bfcefd664ccf32dd50552bbf064175caafe59 Mon Sep 17 00:00:00 2001 From: Brian Meeker Date: Wed, 24 Feb 2016 11:29:08 -0500 Subject: [PATCH 013/147] Updated config.js.sample link to not try and display as image. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e55c6a8..6756a67 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ All other dependencies are loaded with [npm](https://www.npmjs.com/). All depend * [express](http://expressjs.com/) - Minimalist web framework for Node.js ### config.js -config.js contains information specific to your app, such as your client_id and client_secret. Create this file by copying ![config.js.sample](/credit-offers/config.js.sample). Be careful not to put config.js into version control. +config.js contains information specific to your app, such as your client_id and client_secret. Create this file by copying [config.js.sample](/credit-offers/config.js.sample). Be careful not to put config.js into version control. ### Start the mock API From the project root: From 01c8adf2f86c5d4a112efd78fe0aec71116c0238 Mon Sep 17 00:00:00 2001 From: rkorsak Date: Wed, 24 Feb 2016 14:29:04 -0500 Subject: [PATCH 014/147] Fix inverted token logic --- credit-offers_mock_api/routes/oauth.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/credit-offers_mock_api/routes/oauth.js b/credit-offers_mock_api/routes/oauth.js index d0e00b3..6cc9a4a 100644 --- a/credit-offers_mock_api/routes/oauth.js +++ b/credit-offers_mock_api/routes/oauth.js @@ -52,15 +52,15 @@ router.post('/oauth20/token', function (req, res, next) { responseBody = { access_token: '5354e3a56036056cffb5a99f368a31cef3aee2a8', token_type: 'Bearer', - expires_in: '900', - refresh_token: 'cV6tIa3UQncpzGgXfufRwZJvVbwZeoQPpsx7YzxdYNY', - id_token: 'eyJraWQiOiIxNDM4NzA2MDM4NTc4IiwiYWxnIjoiUlMyNTYifQ' + expires_in: '900' } } else if (grantType === 'authorization_code' || grantType === 'refresh_token') { responseBody = { access_token: '5354e3a56036056cffb5a99f368a31cef3aee2a8', token_type: 'Bearer', - expires_in: '900' + expires_in: '900', + refresh_token: 'cV6tIa3UQncpzGgXfufRwZJvVbwZeoQPpsx7YzxdYNY', + id_token: 'eyJraWQiOiIxNDM4NzA2MDM4NTc4IiwiYWxnIjoiUlMyNTYifQ' } } else { res.status(400) From 15b9770e0bab140d00b7415a75033b5a02b305d2 Mon Sep 17 00:00:00 2001 From: Brian Meeker Date: Thu, 25 Feb 2016 08:54:09 -0500 Subject: [PATCH 015/147] Added helmet to protect from well-known web vulnerabilities. --- credit-offers/app.js | 2 ++ credit-offers/package.json | 1 + 2 files changed, 3 insertions(+) diff --git a/credit-offers/app.js b/credit-offers/app.js index 9244a89..f161794 100644 --- a/credit-offers/app.js +++ b/credit-offers/app.js @@ -19,6 +19,7 @@ var favicon = require('serve-favicon') var logger = require('morgan') var cookieParser = require('cookie-parser') var bodyParser = require('body-parser') +var helmet = require('helmet') var index = require('./routes/index') var offers = require('./routes/offers') @@ -37,6 +38,7 @@ app.use(bodyParser.json()) app.use(bodyParser.urlencoded({ extended: false })) app.use(cookieParser()) app.use(express.static(path.join(__dirname, 'public'))) +app.use(helmet()) app.use('/', index) app.use('/offers', offers(config.creditOffers)) diff --git a/credit-offers/package.json b/credit-offers/package.json index a0c31e4..a689f9a 100644 --- a/credit-offers/package.json +++ b/credit-offers/package.json @@ -17,6 +17,7 @@ "debug": "~2.2.0", "ejs": "~2.3.3", "express": "~4.13.1", + "helmet": "^1.1.0", "jade": "^1.11.0", "lodash": "^4.1.0", "morgan": "~1.6.1", From 78b3dc4949ecb5c8a80534950ff47d26b4890949 Mon Sep 17 00:00:00 2001 From: Brian Meeker Date: Thu, 25 Feb 2016 09:56:37 -0500 Subject: [PATCH 016/147] Added CSRF protection to the credit offers form. --- credit-offers/app.js | 1 + credit-offers/package.json | 1 + credit-offers/routes/index.js | 9 +++++++-- credit-offers/routes/offers.js | 4 +++- credit-offers/views/index.jade | 1 + 5 files changed, 13 insertions(+), 3 deletions(-) diff --git a/credit-offers/app.js b/credit-offers/app.js index f161794..b89b8f2 100644 --- a/credit-offers/app.js +++ b/credit-offers/app.js @@ -20,6 +20,7 @@ var logger = require('morgan') var cookieParser = require('cookie-parser') var bodyParser = require('body-parser') var helmet = require('helmet') +var csrf = require('csurf') var index = require('./routes/index') var offers = require('./routes/offers') diff --git a/credit-offers/package.json b/credit-offers/package.json index a689f9a..7c49697 100644 --- a/credit-offers/package.json +++ b/credit-offers/package.json @@ -14,6 +14,7 @@ "dependencies": { "body-parser": "~1.13.2", "cookie-parser": "~1.3.5", + "csurf": "^1.8.3", "debug": "~2.2.0", "ejs": "~2.3.3", "express": "~4.13.1", diff --git a/credit-offers/routes/index.js b/credit-offers/routes/index.js index 50bc92a..c476d43 100644 --- a/credit-offers/routes/index.js +++ b/credit-offers/routes/index.js @@ -15,10 +15,15 @@ See the License for the specific language governing permissions and limitations var express = require('express') var router = express.Router() +var csrf = require('csurf') +var csrfProtection = csrf({ cookie: true }) /* GET home page. */ -router.get('/', function (req, res, next) { - res.render('index', { title: 'Credit Offers Sample' }) +router.get('/', csrfProtection, function (req, res, next) { + res.render('index', { + csrfToken: req.csrfToken(), + title: 'Credit Offers Sample' + }) }) module.exports = router diff --git a/credit-offers/routes/offers.js b/credit-offers/routes/offers.js index 90b5333..44dbad0 100644 --- a/credit-offers/routes/offers.js +++ b/credit-offers/routes/offers.js @@ -17,15 +17,17 @@ See the License for the specific language governing permissions and limitations var express = require('express') var _ = require('lodash') +var csrf = require('csurf') var CreditOffersClient = require('../creditOffersClient') var oauth = require('../oauth') module.exports = function (options) { var router = express.Router() var client = new CreditOffersClient(options.client, oauth(options.oauth)) + var csrfProtection = csrf({ cookie: true }) // POST customer info to check for offers - router.post('/', function (req, res, next) { + router.post('/', csrfProtection, function (req, res, next) { var customerInfo = req.body client.getTargetedProductsOffer(customerInfo, function (err, response) { if (err) { return next(err) } diff --git a/credit-offers/views/index.jade b/credit-offers/views/index.jade index e1aa81e..c3adfc9 100644 --- a/credit-offers/views/index.jade +++ b/credit-offers/views/index.jade @@ -24,6 +24,7 @@ block modals div.modal-dialog div.modal-content form(action='/offers', method='POST') + input(type="hidden" name="_csrf" value="#{csrfToken}") div.modal-header button.close(type='button', data-dismiss='modal', aria-label='close') span(aria-hidden='true') From f1ac399584e6facd3bfe82b6aab78d52ef8e4daa Mon Sep 17 00:00:00 2001 From: rkorsak Date: Fri, 26 Feb 2016 09:46:43 -0500 Subject: [PATCH 017/147] Update README with best practices section --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 6756a67..0e35899 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,9 @@ This will submit a request to the Credit Offers endpoint and redirect the user t To get a deeper look at the messages being passed, start the app with the following command `DEBUG=credit-offers:* NODE_DEBUG=request npm start`. This will activate detailed debug logging to the console, showing the details of the request to the API and the response received. +## Best Practices +This application makes use of the [helmet](https://www.npmjs.com/package/helmet) library for safer http headers, and the [csurf](https://www.npmjs.com/package/csurf) library to avoid cross-site request forgery attacks. However, when developing and hosting a real world application, make sure to be aware of the [security](http://expressjs.com/en/advanced/best-practice-security.html) and [performance](http://expressjs.com/en/advanced/best-practice-performance.html) best practices for the Express framework. In particular, hosting with TLS is strongly recommended and free certificates can be acquired at https://letsencrypt.org/. + ## Architecture This is a [Node.js](https://nodejs.org) 4.x and higher app built with [Express](http://expressjs.com/) 4.13.1. Because of the simple nature, there is no session management or data persistence. From 55995753edd079b908a01cbbda7af37dc8ae7aa1 Mon Sep 17 00:00:00 2001 From: rkorsak Date: Wed, 2 Mar 2016 12:41:33 -0500 Subject: [PATCH 018/147] Fix "targeted-products-offer" URL mistake --- credit-offers/creditOffersClient.js | 2 +- credit-offers_mock_api/routes/index.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/credit-offers/creditOffersClient.js b/credit-offers/creditOffersClient.js index edce800..cab9da4 100644 --- a/credit-offers/creditOffersClient.js +++ b/credit-offers/creditOffersClient.js @@ -52,7 +52,7 @@ CreditOffersClient.prototype.getTargetedProductsOffer = function getTargetedProd var reqOptions = { baseUrl: client.options.url, - url: '/credit-cards/targeted-products-offer', + url: '/credit-cards/targeted-product-offers', method: 'GET', qs: customerInfo, headers: { diff --git a/credit-offers_mock_api/routes/index.js b/credit-offers_mock_api/routes/index.js index 673270a..8e96e2c 100644 --- a/credit-offers_mock_api/routes/index.js +++ b/credit-offers_mock_api/routes/index.js @@ -93,7 +93,7 @@ var fakeProducts = { } /* GET home page. */ -router.get('/credit-cards/targeted-products-offer', function (req, res, next) { +router.get('/credit-cards/targeted-product-offers', function (req, res, next) { console.info(req.query) var creditRating = req.query.selfAssessedCreditRating var response = {} From 31b66fc034f23c8f883a4fd0249c91ed71591968 Mon Sep 17 00:00:00 2001 From: rkorsak Date: Wed, 2 Mar 2016 12:50:30 -0500 Subject: [PATCH 019/147] Make birth date optional in UI --- credit-offers/views/includes/customer-form.jade | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/credit-offers/views/includes/customer-form.jade b/credit-offers/views/includes/customer-form.jade index ccdc718..8c7236c 100644 --- a/credit-offers/views/includes/customer-form.jade +++ b/credit-offers/views/includes/customer-form.jade @@ -99,7 +99,7 @@ mixin stateOptions() +stateOptions() +textinput('postalCode', 'Zip Code', true) +textinput('taxId', 'Last Four Digits of SSN', true) -+textinput('dateOfBirth', 'Birth Date', true)(placeholder='YYYY-MM-DD') ++textinput('dateOfBirth', 'Birth Date', false)(placeholder='YYYY-MM-DD') div.form-note The below information is optional but can be used to fine tune the offers returned +textinput('emailAddress', 'Email Address') From c87cbe13af9fba797b54ffe8abccdce7168c8378 Mon Sep 17 00:00:00 2001 From: Lorinda Brandon Date: Thu, 3 Mar 2016 08:33:50 -0500 Subject: [PATCH 020/147] Added files via upload Added notices.txt to identify dependencies --- credit-offers_notices.txt | 610 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 610 insertions(+) create mode 100644 credit-offers_notices.txt diff --git a/credit-offers_notices.txt b/credit-offers_notices.txt new file mode 100644 index 0000000..810bd43 --- /dev/null +++ b/credit-offers_notices.txt @@ -0,0 +1,610 @@ +├─ accepts@1.2.13 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/jshttp/accepts +├─ acorn-globals@1.0.9 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/ForbesLindesay/acorn-globals +├─ acorn@1.2.2 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/marijnh/acorn +├─ acorn@2.7.0 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/ternjs/acorn +├─ align-text@0.1.4 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/jonschlinkert/align-text +├─ amdefine@1.0.0 +│ ├─ licenses: BSD-3-Clause AND MIT +│ └─ repository: https://github.com/jrburke/amdefine +├─ ansi-regex@2.0.0 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/sindresorhus/ansi-regex +├─ ansi-styles@2.2.0 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/chalk/ansi-styles +├─ array-flatten@1.1.1 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/blakeembrey/array-flatten +├─ asap@1.0.0 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/kriskowal/asap +├─ asn1@0.2.3 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/mcavage/node-asn1 +├─ assert-plus@0.2.0 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/mcavage/node-assert-plus +├─ assert-plus@1.0.0 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/mcavage/node-assert-plus +├─ async@0.2.10 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/caolan/async +├─ async@1.5.2 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/caolan/async +├─ aws-sign2@0.6.0 +│ ├─ licenses: Apache-2.0 +│ └─ repository: https://github.com/mikeal/aws-sign +├─ aws4@1.3.1 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/mhart/aws4 +├─ base64-url@1.2.1 +│ ├─ licenses: ISC +│ └─ repository: https://github.com/joaquimserafim/base64-url +├─ basic-auth@1.0.3 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/jshttp/basic-auth +├─ bl@1.0.3 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/rvagg/bl +├─ body-parser@1.13.3 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/expressjs/body-parser +├─ boom@2.10.1 +│ ├─ licenses: BSD-3-Clause +│ └─ repository: https://github.com/hapijs/boom +├─ bytes@2.1.0 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/visionmedia/bytes.js +├─ bytes@2.2.0 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/visionmedia/bytes.js +├─ camelcase@1.2.1 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/sindresorhus/camelcase +├─ camelize@1.0.0 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/substack/camelize +├─ caseless@0.11.0 +│ ├─ licenses: Apache-2.0 +│ └─ repository: https://github.com/mikeal/caseless +├─ center-align@0.1.3 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/jonschlinkert/center-align +├─ chalk@1.1.1 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/chalk/chalk +├─ character-parser@1.2.1 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/ForbesLindesay/character-parser +├─ clean-css@3.4.10 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/jakubpawlowicz/clean-css +├─ cliui@2.1.0 +│ ├─ licenses: ISC +│ └─ repository: https://github.com/bcoe/cliui +├─ color-convert@1.0.0 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/qix-/color-convert +├─ combined-stream@1.0.5 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/felixge/node-combined-stream +├─ commander@2.6.0 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/tj/commander.js +├─ commander@2.8.1 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/tj/commander.js +├─ commander@2.9.0 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/tj/commander.js +├─ connect@3.4.1 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/senchalabs/connect +├─ constantinople@3.0.2 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/ForbesLindesay/constantinople +├─ content-disposition@0.5.1 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/jshttp/content-disposition +├─ content-security-policy-builder@1.0.0 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/helmetjs/content-security-policy-builder +├─ content-type@1.0.1 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/jshttp/content-type +├─ cookie-parser@1.3.5 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/expressjs/cookie-parser +├─ cookie-signature@1.0.6 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/visionmedia/node-cookie-signature +├─ cookie@0.1.3 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/jshttp/cookie +├─ cookie@0.1.5 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/jshttp/cookie +├─ core-util-is@1.0.2 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/isaacs/core-util-is +├─ credit-offers@0.0.1 +│ └─ licenses: Apache-2.0 +├─ cryptiles@2.0.5 +│ ├─ licenses: BSD-3-Clause +│ └─ repository: https://github.com/hapijs/cryptiles +├─ csrf@3.0.1 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/pillarjs/csrf +├─ css-parse@1.0.4 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/reworkcss/css-parse +├─ css-stringify@1.0.5 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/reworkcss/css-stringify +├─ css@1.0.8 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/reworkcss/css +├─ csurf@1.8.3 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/expressjs/csurf +├─ dashdash@1.13.0 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/trentm/node-dashdash +├─ dashify@0.2.1 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/jonschlinkert/dashify +├─ debug@2.2.0 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/visionmedia/debug +├─ decamelize@1.1.2 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/sindresorhus/decamelize +├─ delayed-stream@1.0.0 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/felixge/node-delayed-stream +├─ depd@1.0.1 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/dougwilson/nodejs-depd +├─ depd@1.1.0 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/dougwilson/nodejs-depd +├─ destroy@1.0.4 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/stream-utils/destroy +├─ dns-prefetch-control@0.1.0 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/helmetjs/dns-prefetch-control +├─ dont-sniff-mimetype@1.0.0 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/helmetjs/dont-sniff-mimetype +├─ ecc-jsbn@0.1.1 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/quartzjer/ecc-jsbn +├─ ee-first@1.1.1 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/jonathanong/ee-first +├─ ejs@2.3.4 +│ ├─ licenses: Apache-2.0 +│ └─ repository: https://github.com/mde/ejs +├─ escape-html@1.0.3 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/component/escape-html +├─ escape-string-regexp@1.0.5 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/sindresorhus/escape-string-regexp +├─ etag@1.7.0 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/jshttp/etag +├─ express@4.13.4 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/expressjs/express +├─ extend@3.0.0 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/justmoon/node-extend +├─ extsprintf@1.0.2 +│ ├─ licenses: MIT* +│ └─ repository: https://github.com/davepacheco/node-extsprintf +├─ finalhandler@0.4.1 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/pillarjs/finalhandler +├─ forever-agent@0.6.1 +│ ├─ licenses: Apache-2.0 +│ └─ repository: https://github.com/mikeal/forever-agent +├─ form-data@1.0.0-rc3 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/form-data/form-data +├─ forwarded@0.1.0 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/jshttp/forwarded +├─ frameguard@1.1.0 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/helmetjs/frameguard +├─ fresh@0.3.0 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/jshttp/fresh +├─ generate-function@2.0.0 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/mafintosh/generate-function +├─ generate-object-property@1.2.0 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/mafintosh/generate-object-property +├─ graceful-readlink@1.0.1 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/zhiyelee/graceful-readlink +├─ har-validator@2.0.6 +│ ├─ licenses: ISC +│ └─ repository: https://github.com/ahmadnassri/har-validator +├─ has-ansi@2.0.0 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/sindresorhus/has-ansi +├─ hawk@3.1.3 +│ ├─ licenses: BSD-3-Clause +│ └─ repository: https://github.com/hueniverse/hawk +├─ helmet-csp@1.1.0 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/helmetjs/csp +├─ helmet@1.2.0 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/helmetjs/helmet +├─ hide-powered-by@1.0.0 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/helmetjs/hide-powered-by +├─ hoek@2.16.3 +│ ├─ licenses: BSD-3-Clause +│ └─ repository: https://github.com/hapijs/hoek +├─ hpkp@1.0.0 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/helmetjs/hpkp +├─ hsts@1.0.0 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/helmetjs/hsts +├─ http-errors@1.3.1 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/jshttp/http-errors +├─ http-signature@1.1.1 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/joyent/node-http-signature +├─ iconv-lite@0.4.11 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/ashtuchkin/iconv-lite +├─ iconv-lite@0.4.13 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/ashtuchkin/iconv-lite +├─ ienoopen@1.0.0 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/helmetjs/ienoopen +├─ inherits@2.0.1 +│ ├─ licenses: ISC +│ └─ repository: https://github.com/isaacs/inherits +├─ ipaddr.js@1.0.5 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/whitequark/ipaddr.js +├─ is-buffer@1.1.2 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/feross/is-buffer +├─ is-my-json-valid@2.13.1 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/mafintosh/is-my-json-valid +├─ is-promise@1.0.1 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/then/is-promise +├─ is-promise@2.1.0 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/then/is-promise +├─ is-property@1.0.2 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/mikolalysenko/is-property +├─ is-typedarray@1.0.0 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/hughsk/is-typedarray +├─ isarray@0.0.1 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/juliangruber/isarray +├─ isstream@0.1.2 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/rvagg/isstream +├─ jade@1.11.0 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/jadejs/jade +├─ jodid25519@1.0.2 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/meganz/jodid25519 +├─ jsbn@0.1.0 +│ ├─ licenses: BSD +│ └─ repository: https://github.com/andyperlitch/jsbn +├─ json-schema@0.2.2 +│ ├─ licenses +│ │ ├─ 0: AFLv2.1 +│ │ └─ 1: BSD +│ └─ repository: https://github.com/kriszyp/json-schema +├─ json-stringify-safe@5.0.1 +│ ├─ licenses: ISC +│ └─ repository: https://github.com/isaacs/json-stringify-safe +├─ jsonpointer@2.0.0 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/janl/node-jsonpointer +├─ jsprim@1.2.2 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/davepacheco/node-jsprim +├─ jstransformer@0.0.2 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/jstransformers/jstransformer +├─ kind-of@3.0.2 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/jonschlinkert/kind-of +├─ lazy-cache@1.0.3 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/jonschlinkert/lazy-cache +├─ lodash._baseeach@4.1.0 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/lodash/lodash +├─ lodash._baseiteratee@4.5.1 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/lodash/lodash +├─ lodash._basereduce@3.0.2 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/lodash/lodash +├─ lodash.assign@4.0.4 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/lodash/lodash +├─ lodash.isfunction@3.0.8 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/lodash/lodash +├─ lodash.isstring@4.0.1 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/lodash/lodash +├─ lodash.keys@4.0.4 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/lodash/lodash +├─ lodash.reduce@4.2.0 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/lodash/lodash +├─ lodash.rest@4.0.1 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/lodash/lodash +├─ lodash.some@4.2.0 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/lodash/lodash +├─ lodash@4.5.1 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/lodash/lodash +├─ longest@1.0.1 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/jonschlinkert/longest +├─ lru-cache@4.0.0 +│ ├─ licenses: ISC +│ └─ repository: https://github.com/isaacs/node-lru-cache +├─ media-typer@0.3.0 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/jshttp/media-typer +├─ merge-descriptors@1.0.1 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/component/merge-descriptors +├─ methods@1.1.2 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/jshttp/methods +├─ mime-db@1.22.0 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/jshttp/mime-db +├─ mime-types@2.1.10 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/jshttp/mime-types +├─ mime@1.3.4 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/broofa/node-mime +├─ minimist@0.0.8 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/substack/minimist +├─ mkdirp@0.5.1 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/substack/node-mkdirp +├─ morgan@1.6.1 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/expressjs/morgan +├─ ms@0.7.1 +│ ├─ licenses: MIT* +│ └─ repository: https://github.com/guille/ms.js +├─ negotiator@0.5.3 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/jshttp/negotiator +├─ nocache@1.0.0 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/helmetjs/nocache +├─ node-uuid@1.4.7 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/broofa/node-uuid +├─ oauth-sign@0.8.1 +│ ├─ licenses: Apache-2.0 +│ └─ repository: https://github.com/mikeal/oauth-sign +├─ on-finished@2.3.0 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/jshttp/on-finished +├─ on-headers@1.0.1 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/jshttp/on-headers +├─ optimist@0.3.7 +│ ├─ licenses: MIT/X11 +│ └─ repository: https://github.com/substack/node-optimist +├─ parseurl@1.3.1 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/pillarjs/parseurl +├─ path-to-regexp@0.1.7 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/component/path-to-regexp +├─ pinkie-promise@2.0.0 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/floatdrop/pinkie-promise +├─ pinkie@2.0.4 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/floatdrop/pinkie +├─ platform@1.3.1 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/bestiejs/platform.js +├─ process-nextick-args@1.0.6 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/calvinmetcalf/process-nextick-args +├─ promise@2.0.0 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/then/promise +├─ promise@6.1.0 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/then/promise +├─ proxy-addr@1.0.10 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/jshttp/proxy-addr +├─ pseudomap@1.0.2 +│ ├─ licenses: ISC +│ └─ repository: https://github.com/isaacs/pseudomap +├─ qs@4.0.0 +│ ├─ licenses: BSD-3-Clause +│ └─ repository: https://github.com/hapijs/qs +├─ qs@6.0.2 +│ ├─ licenses: BSD-3-Clause +│ └─ repository: https://github.com/ljharb/qs +├─ random-bytes@1.0.0 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/crypto-utils/random-bytes +├─ range-parser@1.0.3 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/jshttp/range-parser +├─ raw-body@2.1.5 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/stream-utils/raw-body +├─ readable-stream@2.0.5 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/nodejs/readable-stream +├─ repeat-string@1.5.4 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/jonschlinkert/repeat-string +├─ request@2.69.0 +│ ├─ licenses: Apache-2.0 +│ └─ repository: https://github.com/request/request +├─ right-align@0.1.3 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/jonschlinkert/right-align +├─ rndm@1.2.0 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/crypto-utils/rndm +├─ scmp@1.0.0 +│ ├─ licenses: BSD +│ └─ repository: https://github.com/freewil/scmp +├─ send@0.13.1 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/pillarjs/send +├─ serve-favicon@2.3.0 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/expressjs/serve-favicon +├─ serve-static@1.10.2 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/expressjs/serve-static +├─ sntp@1.0.9 +│ ├─ licenses: BSD +│ └─ repository: https://github.com/hueniverse/sntp +├─ source-map@0.1.43 +│ ├─ licenses: BSD +│ └─ repository: https://github.com/mozilla/source-map +├─ source-map@0.4.4 +│ ├─ licenses: BSD-3-Clause +│ └─ repository: https://github.com/mozilla/source-map +├─ source-map@0.5.3 +│ ├─ licenses: BSD-3-Clause +│ └─ repository: https://github.com/mozilla/source-map +├─ sshpk@1.7.4 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/arekinath/node-sshpk +├─ statuses@1.2.1 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/jshttp/statuses +├─ string_decoder@0.10.31 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/rvagg/string_decoder +├─ stringstream@0.0.5 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/mhart/StringStream +├─ strip-ansi@3.0.1 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/chalk/strip-ansi +├─ supports-color@2.0.0 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/chalk/supports-color +├─ tough-cookie@2.2.1 +│ ├─ licenses: BSD-3-Clause +│ └─ repository: https://github.com/SalesforceEng/tough-cookie +├─ transformers@2.1.0 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/ForbesLindesay/transformers +├─ tunnel-agent@0.4.2 +│ ├─ licenses: Apache-2.0 +│ └─ repository: https://github.com/mikeal/tunnel-agent +├─ tweetnacl@0.14.1 +│ ├─ licenses: Public Domain +│ └─ repository: https://github.com/dchest/tweetnacl-js +├─ type-is@1.6.12 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/jshttp/type-is +├─ uglify-js@2.2.5 +│ ├─ licenses: BSD +│ └─ repository: https://github.com/mishoo/UglifyJS2 +├─ uglify-js@2.6.2 +│ ├─ licenses: BSD-2-Clause +│ └─ repository: https://github.com/mishoo/UglifyJS2 +├─ uglify-to-browserify@1.0.2 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/ForbesLindesay/uglify-to-browserify +├─ uid-safe@2.1.0 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/crypto-utils/uid-safe +├─ unpipe@1.0.0 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/stream-utils/unpipe +├─ util-deprecate@1.0.2 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/TooTallNate/util-deprecate +├─ utils-merge@1.0.0 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/jaredhanson/utils-merge +├─ vary@1.0.1 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/jshttp/vary +├─ verror@1.3.6 +│ ├─ licenses: MIT* +│ └─ repository: https://github.com/davepacheco/node-verror +├─ void-elements@2.0.1 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/hemanth/void-elements +├─ window-size@0.1.0 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/jonschlinkert/window-size +├─ with@4.0.3 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/ForbesLindesay/with +├─ wordwrap@0.0.2 +│ ├─ licenses: MIT/X11 +│ └─ repository: https://github.com/substack/node-wordwrap +├─ wordwrap@0.0.3 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/substack/node-wordwrap +├─ x-xss-protection@1.0.0 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/helmetjs/x-xss-protection +├─ xtend@4.0.1 +│ ├─ licenses: MIT +│ └─ repository: https://github.com/Raynos/xtend +├─ yallist@2.0.0 +│ ├─ licenses: ISC +│ └─ repository: https://github.com/isaacs/yallist +└─ yargs@3.10.0 + ├─ licenses: MIT + └─ repository: https://github.com/bcoe/yargs From d60c2394743f2ead23df05401f584a204fac11e3 Mon Sep 17 00:00:00 2001 From: Brian Meeker Date: Sat, 5 Mar 2016 15:03:18 -0500 Subject: [PATCH 021/147] Removed mock Credit Offers API and updated reference app to point to sandbox. --- README.md | 6 - credit-offers/app.js => app.js | 0 {credit-offers/bin => bin}/www | 0 .../config.js.sample => config.js.sample | 4 +- credit-offers_mock_api/app.js | 56 ------- credit-offers_mock_api/bin/www | 90 ----------- credit-offers_mock_api/package.json | 17 --- ...eric-VentureOne-EMV-flat-244x154-06-15.png | Bin 23386 -> 0 bytes ...ericEMV-Venture-EMV-flat-244x154-06-15.png | Bin 21775 -> 0 bytes ...0-MC-Blue-Steel-EMV-flat-244x154-06-15.png | Bin 16768 -> 0 bytes .../images/www-venture-visa-sig-flat-9-14.png | Bin 21550 -> 0 bytes credit-offers_mock_api/routes/index.js | 141 ------------------ credit-offers_mock_api/routes/oauth.js | 72 --------- credit-offers_mock_api/views/authorize.jade | 9 -- credit-offers_mock_api/views/error.jade | 19 --- credit-offers_mock_api/views/layout.jade | 21 --- ...itOffersClient.js => creditOffersClient.js | 0 {credit-offers/oauth => oauth}/index.js | 0 credit-offers/package.json => package.json | 0 .../public => public}/stylesheets/style.css | 0 {credit-offers/routes => routes}/index.js | 0 {credit-offers/routes => routes}/offers.js | 0 {credit-offers/views => views}/error.jade | 0 .../includes/customer-form.jade | 0 {credit-offers/views => views}/index.jade | 0 {credit-offers/views => views}/layout.jade | 0 {credit-offers/views => views}/offers.jade | 0 27 files changed, 2 insertions(+), 433 deletions(-) rename credit-offers/app.js => app.js (100%) rename {credit-offers/bin => bin}/www (100%) rename credit-offers/config.js.sample => config.js.sample (91%) delete mode 100644 credit-offers_mock_api/app.js delete mode 100644 credit-offers_mock_api/bin/www delete mode 100644 credit-offers_mock_api/package.json delete mode 100644 credit-offers_mock_api/public/images/JB16760-Generic-VentureOne-EMV-flat-244x154-06-15.png delete mode 100644 credit-offers_mock_api/public/images/JB16760-GenericEMV-Venture-EMV-flat-244x154-06-15.png delete mode 100644 credit-offers_mock_api/public/images/JB16760-MC-Blue-Steel-EMV-flat-244x154-06-15.png delete mode 100644 credit-offers_mock_api/public/images/www-venture-visa-sig-flat-9-14.png delete mode 100644 credit-offers_mock_api/routes/index.js delete mode 100644 credit-offers_mock_api/routes/oauth.js delete mode 100644 credit-offers_mock_api/views/authorize.jade delete mode 100644 credit-offers_mock_api/views/error.jade delete mode 100644 credit-offers_mock_api/views/layout.jade rename credit-offers/creditOffersClient.js => creditOffersClient.js (100%) rename {credit-offers/oauth => oauth}/index.js (100%) rename credit-offers/package.json => package.json (100%) rename {credit-offers/public => public}/stylesheets/style.css (100%) rename {credit-offers/routes => routes}/index.js (100%) rename {credit-offers/routes => routes}/offers.js (100%) rename {credit-offers/views => views}/error.jade (100%) rename {credit-offers/views => views}/includes/customer-form.jade (100%) rename {credit-offers/views => views}/index.jade (100%) rename {credit-offers/views => views}/layout.jade (100%) rename {credit-offers/views => views}/offers.jade (100%) diff --git a/README.md b/README.md index 0e35899..b127443 100644 --- a/README.md +++ b/README.md @@ -17,12 +17,6 @@ All other dependencies are loaded with [npm](https://www.npmjs.com/). All depend ### config.js config.js contains information specific to your app, such as your client_id and client_secret. Create this file by copying [config.js.sample](/credit-offers/config.js.sample). Be careful not to put config.js into version control. -### Start the mock API -From the project root: -`cd credit-offers_mock_api` -`npm install` -`npm start` - ### Start the app From the project root: `cd credit-offers` diff --git a/credit-offers/app.js b/app.js similarity index 100% rename from credit-offers/app.js rename to app.js diff --git a/credit-offers/bin/www b/bin/www similarity index 100% rename from credit-offers/bin/www rename to bin/www diff --git a/credit-offers/config.js.sample b/config.js.sample similarity index 91% rename from credit-offers/config.js.sample rename to config.js.sample index bb6c7a2..b9b32c9 100644 --- a/credit-offers/config.js.sample +++ b/config.js.sample @@ -18,8 +18,8 @@ See the License for the specific language governing permissions and limitations * config.js should not be under version control since it contains your * client_id and client_secret. */ -var oauthHost = 'http://localhost:3002' -var creditOffersHost = 'http://localhost:3002' +var oauthHost = 'https://api-sandbox.capitalone.com' +var creditOffersHost = 'https://api-sandbox.capitalone.com' module.exports = { // Settings for connecting to the Credit Offers API diff --git a/credit-offers_mock_api/app.js b/credit-offers_mock_api/app.js deleted file mode 100644 index b8a6210..0000000 --- a/credit-offers_mock_api/app.js +++ /dev/null @@ -1,56 +0,0 @@ -var express = require('express') -var path = require('path') -var logger = require('morgan') -var cookieParser = require('cookie-parser') -var bodyParser = require('body-parser') - -var routes = require('./routes/index') -var oauth = require('./routes/oauth') - -var app = express() - -// view engine setup -app.set('views', path.join(__dirname, 'views')) -app.set('view engine', 'jade') - -app.use(logger('dev')) -app.use(bodyParser.json()) -app.use(bodyParser.urlencoded({ extended: false })) -app.use(cookieParser()) -app.use(express.static(path.join(__dirname, 'public'))) - -app.use('/', routes) -app.use('/oauth/', oauth) - -// catch 404 and forward to error handler -app.use(function (req, res, next) { - var err = new Error('Not Found') - err.status = 404 - next(err) -}) - -// error handlers - -// development error handler -// will print stacktrace -if (app.get('env') === 'development') { - app.use(function (err, req, res, next) { - res.status(err.status || 500) - res.render('error', { - message: err.message, - error: err - }) - }) -} - -// production error handler -// no stacktraces leaked to user -app.use(function (err, req, res, next) { - res.status(err.status || 500) - res.render('error', { - message: err.message, - error: {} - }) -}) - -module.exports = app diff --git a/credit-offers_mock_api/bin/www b/credit-offers_mock_api/bin/www deleted file mode 100644 index ec0dad6..0000000 --- a/credit-offers_mock_api/bin/www +++ /dev/null @@ -1,90 +0,0 @@ -#!/usr/bin/env node - -/** - * Module dependencies. - */ - -var app = require('../app') -var debug = require('debug')('credit-offers_mock_api:server') -var http = require('http') - -/** - * Get port from environment and store in Express. - */ - -var port = normalizePort(process.env.PORT || '3002') -app.set('port', port) - -/** - * Create HTTP server. - */ - -var server = http.createServer(app) - -/** - * Listen on provided port, on all network interfaces. - */ - -server.listen(port) -server.on('error', onError) -server.on('listening', onListening) - -/** - * Normalize a port into a number, string, or false. - */ - -function normalizePort (val) { - var port = parseInt(val, 10) - - if (isNaN(port)) { - // named pipe - return val - } - - if (port >= 0) { - // port number - return port - } - - return false -} - -/** - * Event listener for HTTP server "error" event. - */ - -function onError (error) { - if (error.syscall !== 'listen') { - throw error - } - - var bind = typeof port === 'string' - ? 'Pipe ' + port - : 'Port ' + port - - // handle specific listen errors with friendly messages - switch (error.code) { - case 'EACCES': - console.error(bind + ' requires elevated privileges') - process.exit(1) - break - case 'EADDRINUSE': - console.error(bind + ' is already in use') - process.exit(1) - break - default: - throw error - } -} - -/** - * Event listener for HTTP server "listening" event. - */ - -function onListening () { - var addr = server.address() - var bind = typeof addr === 'string' - ? 'pipe ' + addr - : 'port ' + addr.port - debug('Listening on ' + bind) -} diff --git a/credit-offers_mock_api/package.json b/credit-offers_mock_api/package.json deleted file mode 100644 index d6c90f9..0000000 --- a/credit-offers_mock_api/package.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "credit_offers_mock_api", - "version": "0.0.0", - "private": true, - "scripts": { - "start": "node ./bin/www" - }, - "dependencies": { - "body-parser": "~1.13.2", - "cookie-parser": "~1.3.5", - "debug": "~2.2.0", - "express": "~4.13.1", - "jade": "^1.11.0", - "morgan": "~1.6.1", - "request": "^2.69.0" - } -} diff --git a/credit-offers_mock_api/public/images/JB16760-Generic-VentureOne-EMV-flat-244x154-06-15.png b/credit-offers_mock_api/public/images/JB16760-Generic-VentureOne-EMV-flat-244x154-06-15.png deleted file mode 100644 index 0e18f499a35f9cad888f9608b3dd1804b6fbfb34..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 23386 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 diff --git a/credit-offers_mock_api/public/images/www-venture-visa-sig-flat-9-14.png b/credit-offers_mock_api/public/images/www-venture-visa-sig-flat-9-14.png deleted file mode 100644 index d03e705c319bda101b196bd68bd08b02d37576c0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 21550 zcmV(^K-IsAP)02Vd?96taj zOaK=-02DOj*Jpd_A03t^KDo_9oEC3}) z05M(wHedidb^s7A05@p>A3*>(ZU8o701qz!Hf8`cVgNmS03JdBK!X4+Rsaep04q@d zGGhP#{{T9100bcbEn5I8RRBYZ047lYBT4`O3+3hJkCdI=+};2$S^)tUb$NqMPg}LL zxK?U^#>B^KYjYPUL!Fb4LDg+SJTzmsjIU4`S>qNXJKJ$ z5k6)O5G5iyS*<55Jyvi)KTH4=LBuI7D=RtxBVhnBbS)Vt03lk9Ff?f#BvmFc;{kxx z0000EbW%=J)!EwB*#Q35+uMUBuiE7tm8}2(QkO|YK~#9!oSAuZ+f)?ALxHf4p7dl3 zB3YXytOd()n4p z@%q-lz6(Q3!}Gt_2Op>_%?B`8M<0KpP!;gU#V>A9#Cs*P^{LO4C-bmo4733 zT^H{~6o}3jK_H^ov2nFCA?$4&$K7__bltj%4UyS~;Kj4C3_~*v7*F#I!}Byv<2RaG zo@r_t4&CH}!HurNU=1vsx@8RpxE)xy;gL)&tRzV+9OERJCgbs#=i62NX)1Ma!M40| zS;<}dsRD+)?`2=6rM+3tVt2Ej70o&eW*ZS?QJ&7yy_Xqw+0OExWGHG|x8p(QqLp0YOSbUNV{_*R_GCa0eQAVax$hEfc6Pr0&<0usTeS zI~|iBz|(0m1s&kYJRQ@hl~e#%2ci43P1?cmBoyrjpD5r*g%Io5&xG5gX;C_XaD)&M zjOzy50HS81>+sdywHNq7DPXcXj@Jf|HHh2=+op5ZjW0A~vB0;6L6HIL@eb;eU85l< z-vV{T8h{pWfnq{qT)}ISR3Te9X%sop!9C?WARt}=L7_ZB6*Ae0*sm(-g|_r|Tv-;N ztq%zN@t4I()}`=SQI~5!3j#+*K@bIw@>&t#LF^l-7&{qs&xX#zz=OLf>l*0VVh`G2 z4X^GRsV(5s@MPdpYpSg1K?z!r!oz_JcmVSNuMR_8lPAcr$WM9;Y7@8zCo(xLo{7>I zw74p{;p7RgJmCRAZIc0@^#xu*+g5Gx#oedxAAR!KtZDpxBSqvuSvvqBFt<(=;0e5R zV-ev+q}@VDLzICq!npI`z3Vv8f`w;yO)2dz4%h2=1I7R@ENmsH03rZ|rqM3w%s{8I z3f7_l5}64(&?qdQ6{Tiz4NQ75(4j0JK}Y6B+dvv_RN0j*JW% z+xY!WQ0{9GOWojF7{G!B9YnTGy0Qmc-GjC;h-El04HP9l4ZZ;WNX*Avuxi>ffj3<&Jd4rQmCvTk z`)|Mfh6~o~H?P`0d+Sx3wVxh+SVC7PK!VyA#8k(AkC5!jJs;FUlsbWJBd}u#oHjw` zAXr7WhxS+oGI(4Y2xGW~VF*>0n5-GjEK7aTvMD%t@g~3tAMe2x`JYB7Dy}#gzN}0= zI6#R^jVmGD0$^9%M4+#AnPS!AnHoIrA~NWX7`!e%b2TpLu_iU z;ew@noIXup<s&8T-80 zWSJM`u@)^v>4I1@P^UJc19G#4ln^)*OwsnjR*4$q>hZ541;tsN{^8qw>jA%3IfRoTTS(3S^GB0pzNUAb7t_)!v*(=fSDu9ir8+|HI zLios7caZtV2yLwP4#edB?XFcp3}X52(W6;|ytEGm;@D>}J3b7cLNEt1l1grrD!B)8 zQRE$jlj&qNnG`}VWju321g(IJae=1CLvwQscG&VvL+$xMXYR8g`L>n1C?ZtO+I zvSmg^)7bl%ncgF>ZDe*|mJAw0WvSksO?WnyWU~bqhO~s#6)la5U7Fs7^|7_Zn^Dy% z8K_9ERCbn|@MPfADINUg*c?>mbV_21Ts;67yzMj`R9eNtR2?0CSd>0gt!0G&4z%Ew zPMPOLAVY^s`iN=?SC7zeH2I{+C##Kt5YD`EGU@bqh4dY#5{1f5)3ul}A#{c<;LdC_ zQH8;tL1>uXrx$%yHu~-ZMT+bmh;hf(gMQt|FU9d3W*%qlgNh(yT-R^)sa);l1rTqC-YqcWLP1TK9P`CzJ=sP~hTpy4UD~i)Jj3iin z=`*J3-abM*+fwA!q~orrJDsfUEKjxVY*nC0FN|3+i}y2xapR*vbL#|T26F~#54@0< zN&9Ve>cQ#gj9)Io6Ik__JXJlO+Pc_I2m0ag^8DuJTt{AtWu#SlV{4Une)H?I>iRnI zAp5kvczV(9?#^GHUqN7$B(z#%O+ITY-GWR>f@lIa76wHvnm|om|*N7MVxXjLgtl>vvM7#-*tL?z{+WBDlFs zQ)T8ZHWRZw{(8Cm=1#x(_4&8oUY=cFKfO4=JiEI)JHPmGxjetPynJ%=bNhJt^X~hz zUA4P?@oLv%jd%LBdGoS0S)8b_TZb;lTsL$>8N$6`oODvGHgI+&K8ysk1B*)_y3=`{ z&f`oB3Z0fi0;}Cp)n4c$N_c@u*{`fgIEYT zNhdkluL#MU(*X}#>Tpeo*B&<(eYe5>8b+vj0Od8oOG$~W;XR1qiRqQImFPNqA z^XcQ|@_2V>+vAI?t0&8=^X1ds_450t%O|^UA?xkM)j8Og%fpkK9s0=j@!9k9A1-fh zzuP_kZl~)g!>xhwRX}~E8$_-PAjk{02#~8u9!1DuI2|9fbpR_x*}?k_fmFSkH;CKG zX)dxTLiJKznYYQmoCzdq?u~_js%$ee11$AG$iwh|G6kHdV%bWWr%d@A;vlelExqN(av%EQ9ez*Jao87oN+`qW} z@oIPT^X0`4Pwrbs=-4@FP}rBQ>z=qx=Z}D1LuW`V>u5hgkqz=xy1?Zxu|na6Y}c;e z(C72jX=;l)-UhXgj&vKr*?~Ai-?^bvcQ!}`YF5TU2|(F;0hD`3pc9$PGpH^MB<4+D zk2MIahFggig_$mcK@!>R*$bqs@4f->&GN;sUoT(a=@{zXFJBygw|sK`_44X!dA56e zw$ser<>ke1i#oO+Ta7QEuTGw@y#=e2M{@2d{;E!9 z-+w8?licjV622nKF6)J58a6gc!a7{Pyt=qO+}~bZ-`+#$>z`g8+LtfQ@9Adgq)-`_pI+5LQT{&;sdzusWZgD$N?mR31y_x{?yFnXWkR1NCB|BxWVX()fX) zYxAm;oIZ^YeHzB{G#MHL!?(4>(zHa_rnNAtXYHXK99rvHySsXE**^|iMw4^~N z3c?A1=Tf-rNZ6jhBC~m2UX(JOp`|7*P_sZl+(jlRb6<l5brgIxIFH%kvkf{4Xb{6&O3eKz9P!rR(&A+M>Zge%1Gy-8+sw{yNKe4RV07@W78x5)txPc)0fY@3j5d@VOiVZxvLTpd9 z0XIzmN#F(*$F(>)+PXgmuA0^MCgGRlwTw2(#skEN4&YH3j=nmD%NA)ma#xeG&Y@c| znx&IT*+Asg1jqaoYmb0-*=xc`xV;q39B#di{2D5AoEX~(aWR9)$YLsf!Hh-2 z9$O6qSe@QBl><)}MJ(jjGNCw^mS|i0w4F(FAuNF%q=*t@5wO$5`g5AeE1a1NXc+AE zr2VwI*SOMcFKNZl3WVF3Opv%*NXo9cDT2fPC`hZylBTI;?rNToLqmz*f1y?obaSUCm zoMT^r8*HUUkhAw>e%#viS*-bmFVa(HYBt8zP?4<+6%G}pY?{PguW zEE>1W(S^$GLdP>DX6J=im7@QfgxuDrEc@X$-UpdAindivFTI4WJh4@ZC+!mn5E$pl3!L<@Gpz7rLi4vo$X0uviP zEm!b=gr6^g%-IMdIm7Iy3-j45l^#hQp#m7yiGNZR{V*UifToo)sKJBeMOwnD8!PgK zMl93Hgae83L1sh3Qn1WgQ(;bVKL&J9iwj3tuquQhJs0@3MQd5&Br?%U19%8tCqDh^ zbciO?9RW(2N!-kb@jQ1>t?(sFaUG86%s>x9Rg@uYAeq(?yCG_DghvrXKtooK(SQ^U z_yA14EE90$e$ZtwjPdHRBiU>OAx+g);$*KCYN8wH2HI7i=>*d8;rwo})eyCaToAG- zL1ZA|v>2_`v{yHw>3Mm}-)W7gT4)u4!8@t}Jgb2T{1YPA#c6Xgk6jev+NY#ob?MqL zwF_DY-1K7REi|A3B?RpMN7EJwh+3= z&|QKWC%46xjm+v<8Zv<7&M7b;<-)UwUT#o|xv9X`!sJ*FJUqt$W&A2u#ohBNi7U-( z4LKg`3jl*}IQ*>6bAO0%W&|^;L&yf$sF=iv@9<=VE0l+!vM&8x#dIix?i3n_0?%p~ zBVv?YTRlsfx^qj!cU!_S7`42M`H zJ||q|oLhsvRG4K5AIIsBaF%`uz5NpX76(GuE~k*V0WgD@yiC^6J#7HIS_>tvA+rU1 zU3Jr}P#ow5_Ig{+YO&EYH6h?2zzF00mopb#hmHejYHG$tVj)dad2W!^y{bo0T0tl9 zapl@L3CzG%y_XWa|1&w`TTms}nyEb_#7F=)#`D^h@VKeR+kCvkO7g^0s&Dx8jl(1`_{4#g%uZZ$-hLw#T-A zl!OEVM3#Utgh&V>1VTs@1gtE|QmxffJ+1Bc{|udZ>H2^?q*Tu^_uXag<);q%aGfye zQVAxE2NsZu1;8PqvaRqHK*nbR)Kxx;*noC`M{`KguZd%8dt*9kh9!llu}J{z+7`mo zz@gr!;AArtD<~9oiQ12@NX>z(BuYBa*^Pi&!X<0O*+*2)8ZRAHBX$8*Y}|1bcP<(@ zZJXQ{aGw8;PHk)%Za`*aRunDAGfaBRMbgTRC%9B;R((V%kP6<5-0)$LY`8QW+X1{9 z*sTc-46qdhAm&y-ZH#cA<*#u3X;z8@?=>^XMCfpfdl5D^Qc7o1z(TAx7n(@sS zUsj9eRT0dAW}e;4s#&cx?5S_n1mI$$isOryM6g11Qs}!Nfe`P-kmAq9g@x>U!pcEO zn=FZG(?E*SgK}=|BJNC(a4M66VdNnOuu-fjp)^Spd@_obzfg5XB`RFp3sS5X02+6V zN|z=iJvF5WdJTSDwRrQFkHeDnszmuPOWOG3b>g+tML>QIKvh^ zG~F2V`@x`D!@a58LYa3#Hjq-+zojS;Td1cgo0t%)y*O1AjQyao7^n5R6gLvXbrH8v zP^r2Es2pHpLL!s^!#>AL9VR1f4crP3hJ}lG2FN5Go65YBEsHEP3E)|3ExP6e3+O>p z0?XZo(fW#jiMJv)fF6_pxZ+RUVa2^7?Eqaxj?G6?gk(lx7`B29;3(iL7mkBG)S$=4 zLd}E$nAoyx>O@Wm)i!6xabkrXvold@an4Xub8Oag$Tih$)~7(gR6c~SN)r`@Ajfu8 zV|W`T;^>lGA`p5p!$pvIS*AlKJQt&r z5bm49bJKFIfuUp#S8NIrO~(ocW7k368QW$;&F2;k3B>}{<^*$3NVkx85${DOu~HeD zy)bDVJTj+}T$F1eYOX<_k%@U$)@0*Gw76!-3HYD9i&Qf#G0`UALM}IK7A49bN<&Ax zv{)_|d!@2jG)v?e7_Y9X*uXmQ;Ry~eNUs1Uzzzh?QVlqP8Bhnezzh)^DF%hEY<_^< zWP``rfimQnW7N{NkNMMZY*Eo;*-)3TVa!LAPLgI$R3UV+?VXnrvzMH^4reiX%%%m@ zoXYf>&;{XwTOMb@vj8d!X9fFa&QX^ES2c`UO*JIBmczt@R72wNC47}>R!Y@U>5^O` zC|7_KTUCUmG9ERvQfct)A|Am*Q{-f2Ty35YSv0|lhhuweUr_35ULXzwx6!d-!+<+B z<%!8m)A60~0@DwjevSn(N(uPuoJ1K9Q>k&E-k7Cug0>bVG&jk$2DXa~7=Tsbsd*DP zvuOw+WQyp-Sj2>?E9xh1nUBU71Z3=#u9-^@V8d`=Ri$Agr$T%ZGCj{R)E;w8dZC&?hc|6B6QQX?>M`)6Xujmq zrcf~vq)_aGLHsA^vS_Y{asRw$+EUPtZD>ENB7{K;ZgCemwcuAwtKoqo+{*O}0Vrm! zMf4&psf9)-=rR!FT+>+t@MY2DF}_5iby;%VY8IrbIL!nOrWvPrmXT}$IS2>HCh!9s zPjPcV`|dfmHDNQIRg_Ra4D@Qk-WUw$A%b*RD2d@1F6LcX8%8I3fL7($Mi6rq?RIQJ zXU+B8TLdM;4NXlxy3ZoXHq4d#DgTcHG5{Y2J5wv^4DyaS{~p6;Vo=>0(%z1 zhbWq)c*tqAg* zc`n}m8ZRc(ZB=Q@ZF#J;m0_lI(6AYp7?#A;R*PZ~Yhwjo8W|Byx=fr`E)p%vcT1kf zVC)~!t$M@SY~Fr$2laZx4bxm9jFWg$F_Ddp!EJ)p8jKqZ-y$lD-YPEeTroZuIxR2k zNbEa7`C-#MTd--~Ko{C@P_K}PhN(VrP+eX1dMG*K&`w*b-V5!HmLs(w7>|Ew<%Z|j zgV?XPT8Yi6S@rLL+!m^Brcx7dP*A8)_rLz)+v#-bAiD&~mt|*aRVvohbC7r_mjB0c zeh};N@yEOEY=`yhI~ufm4Ejk^KXm-t2A%c*^~2b;ZjGj>pU#_#FlmVR6Uw}|(z&qZ ztlRcCt)bYfEzZp5g*fvFu^J#~UuWVCfVbkRSnI56otyK8)i)?&slZ#bz1OlDbQ z9p!A;$XX(4S^Qj`)v%QOSZLoGz<66gyQIb(U$c zqToP4D>ZePtewO$e3ETMG}}z|2t@V32}C5=Xf&aXX6DdZQ@{A)dkYeL5Bb$)soZd< zR&h8cVBf8pKiqBa?g5$W0USSUPrL0JYjrxk|1MiYcN!YC=70t8#C|h$c{60~q-^A< zy@7J+I^&<;q0+Ra4~U)BQ=BIB;b7^~2pmw(fqs3k=NT=KGWMgjd%=-u4ysb$xfY zmpm^Cv|xWD-gtM%EBg3#>Hw|xySwcfJ(n7Zr`bt9`5i0 z5U+_?{;b)aNjbAh!N-yAM^6#w1WX&_HusTYj;+}IV>SABJwyyn6WHmB$xP@v3bNT3W6peLRl-T`!Aq8by;ypk2TG z=jB}_y)DK6LrNGn>eS{bFO@x-^)wHr5+eA!fBa4Oxgs) zSWkD4g5o*W9=1-2RS+( zwd2RJ+wO$EZ|J^r+#GIJ&v?6U+0h<=RaG%VJFH(Lwp_0;hlDe~h;-IZVEBHtzAgwo zZ*e!;9kn1omLOgmeErA)ydx%>PQeha?zW%-@En7B*@`rlg;B)9iApubhn-T-cbQnC zbm;k35e!>H2Mn95<@)92WxWH-kFaZjc)MHfb}y%wm(|8<41BXO2-$0D*2dn)dh{41 zaL}#C(aRC7RCFC}0Dj&`s4l!urre9i!}}e z2id5AuvR4CNTOrT03)sHP1?wx??<~J?&fmcRD%&jCIWDxYS#c91gxMUxNN4@!C!sU zE&+cxExJ>uS{619Yc5rLQ%|v{b}{||;AT4*F?@h<*0AaOC-^NAe0szAo4Os->lKxz z0papveGaNcTTmN@oaZPd2^J&f%aRF?zKB%@crC39SZ* zc+h%u0k}YJ`>Kj)3Wj0F&bfyCG6LWzr)hl&*)w0ihM$I0-an643%ed*^g{r?=34Aq zED`_tq7UT&FzmyJ6j>^i&9RR-TrB%jykwSAFua^`Jv}_(924=6haWSmUhjVT13FNR za%^04E|5O>$KB{IGM#8P+C~Zm535IpN826N_HN@Rb;I^UIrh!y@O<`1idnK(NtkrK zpN$3N0a^f7RTRb$0Su^H!le7Ij#xy7VciJXs2CP-lPj^7htWKW5?9qAZ~^dP$My8I z=tAYm;7y}Xe+m9Ak%4O3%{HeU1(!5y#{0g zE}6X|s->%m5BBp14%A(W!Z^YZD_m64=mGaavid6$THjI@v773RX zHYWx;kETVFUJDXtZ}?CY2?B`j_~$pzn}Qzw`V3dS0bsPsQ8Z;E#~H)An5DMjpl$~Q ztZN)D77Qakg%TLXpSzzuJ*tNn#N71#k!EIghv*g zN+nwORsVgt2IBP&h?iN;HUF36r`54Ms4BNp8`E7E?i4{~9Ko1c!F;re!rOt1X@~&< zACPHDf25nAp?aVk^6h#vkq^%ss1b|P2yYe)OB@Q|a6m@@#vrwxsJXGCwbLBK66_th zmH>j}@lv`RIK!x@mLNn*B#x0NzOF4*E7y-WdC? zGC($60ALw_)9W2l1~RPoQ7((o(|#lv7Vssfk7$&ICEL{M$LqV%a-pIzL%_mmC5eV# zf_Y&z)3=04XI{Dx@T(Ej=}B?cAt&Iga`~zZt1gRJT<$>!z-C^h@2l0z3nCK(5k~ot zPXG8Q&hd|r0Q}Pj^n0YGqJ=8Pd}_7mWpp>gaBDa)V+GaiI0Jl4yw5)5=k_MyxM^zC zH8RX9ak1qAjCANes;-M!@@Pqh^PH&em}ViLyWS!&Uw@bwH#|GQ8_ODHJpp(Z#gZ09 z2k?IUyFw=51@aBtv1E22{tioBnsl!R)vm~}1l+v}_=S-}-mw#W4{C)pW)G$jk9SYA z?Ze}oc!j6_>7!U|U^J~!sa%*SdVEZ$8Zu_qpMO0zCCKk=FpD#M7Hl?~!e$e#SDJ)| z`FTeV=(Hf<8OQ5KrkuI+T&$oAeva9X#mne^p>lj&YU!2=hWGG4`&V^ycACQ*cMGzu zgRjB@3@=<@N4Tv4aHYhvYgRyTrPL#CphfNE z_V=d~=*8OYXw~Z-@Csjz%a1><3+0+poZ5{F#`b(4z8>RR5eR#V;^CkchaOaTFJ0ji zcz!+{cp1GPSrh|k>lvP0>Z4^GoXvTL>p9O+^*nXU>m^VzEag?*FhI46SFmmlxE$UW z^={wQq>0p8{KyzZJ?r`&Q_=zOs)TA>*Z#C3epZDQM$l8-n(+>JM~8h`2(iAW=z+7uWUT#k>%WdYl1jo<20IFsfQlB4MK2{OUm(51jHg5^PF<^mV%lr8a z8X98*Zo&IceZ?>!Xmzs&yK(*l<2=Xp1wA(;j2tK0M zvyHr11UaZZP{P=A+Q`A#M$7)qfZ+94p=Xltvmp9st=U0lo~1Z zyR@Jy99=WOGUi*L*Gnj#B%MQ~gSgnqe{dQrnIlMSn;Izt`qB zx{yJ#lt7;h!CClb0K%3v*jN@t;`^2KTv`je)=xQPbzc1f4BLPVM@?RPm%xta+)6$9 zFN(qKdcU5{fOz(|RijQARIu2J8BV~jS8EhS`vv1Zm>zC|T{rYVb5r3uAEF79vXcse zsap%F2OT#l8%~5vcE)0Q3%M2-H&zbbL{KdGXv=B!ri65akh5=u5TlCifB#kj?pk&! zbg6p55om!@Z5rK{_|8Mg?XkyFgb2^ANRuw|X*rIFcm;+}d#DD;u4Zc_AUGUM<)ll| zJO$*2HANDxfNA1|YHLCHaXhbQ12Fm#Farp;T<}=D8+6s;TR*YiAYiVXX-RqN)p;F@ z5gE6-Hu2;fmaI=EkheH|%DM_hk@eS9UNUW-Scw>mLm!waZ?*f9!cIb=h5kSqAPz$S zC{pPt_RMk*0om<2h{L@e!~n>NpVHac2J0<2`w3sE|LLb6e%k!<%MbXv%&Plw1$CaY zJpc`hMrB;(P=Hc_R*ja53vMf83p95)Byi5JZleE!$x{H9givi@e)`;-6B_m#Nf{4=h{lR%lKZgb}=zX?8jra8q@V>4NrmxzT~t(fLVMC!o{a3>-) zEmfW%*A}K!AF!{kO$n{1DgiI*R#+_kbCH1Df@;_v;dcPnr$EV zKIJ9}^p19-DAS%?IaQO1cuZuNtU){O`-3r?GTX)3eh1cP$@Opr-XPQsVr!VA((K2A z;nc%*(Ul<}dBaF__h131NzC!%Yg$C_U>-r`Hkuyyng!HG?GapV;nquS7jb8Yu3rPW zA@=^_NR{UG4q7%Z)EaCmR{FQ5j~~zHx2GKz-=B`Y=It9b-y!DmPEl|3C{t?HsO_E2 z8fRMo_tq%q5))AZZVH9e2)8>KpR)-wJ#IKKgxQw81F)B*DAb+ z((gn=Yh!R^$IjCNHjQc&pWwX|Vr^j|Hl>za<>#SauEs9^qE6_3|Dv0I`uuF`;fQw< zBl8lb{KX%XcXtI#V`k`k@if+07t@i@u>ZnGmk6+xLxhFd_`9c!eFPDtG~t;Tgex7 zHZdDueTW43Xgn7=VH7?h7b>35X8p&Z^7SR~KKT6joN7pJ83Y`JE+)?xfC)CRrH}GV z91VrA-rOu?sHo#@JqS1DBW71S=C$I-qtBjHaZSLn2tH`M?O>r##o$VL=DY8E*_5); z=^)|S7KX$&&-!FR$e?BsxKolJ2Si*5ew!@I=Mt{o3bhtYe6r*#wwlG6iAK4QGX_fv zE>%XU*{-Xp@qT#am!+$R=yUD7Pfp$)vFmnv1@(#jWvQW_HKFCN>8ARw>PHjDHFfcR zgZjq10gHvFL0vb!0GG61JOzwzj~(+pv;fI-)5qbUAWOs^ILO_J1xi3Lpc-CdF(R*N zV9}<{sr8}JGSk7cTJ2Y3y~r2iW50yPR_PVZ#8{Tl(#e$=TtSu)lTb)AOc! zZgxFe`EjEQK^5~}Qk=(@1FX8E@_vUlZ{U4!_q~EFY?YK-%yNw{hN1kjW@9{uBb*>b z@4+g_+QOj1KaF{Ky;htC5s=x&ga@eVItNdMXWt_ z%s5stg!rqe>vOZSKh1WExN*W8Kx)_fKAuTy#tNB z$-eMJnAGN3HI4??^FfPgB>70#w#ZZBy(bYCCB94%Zq4 zx_s}hgdf+%=}PjTYzbI%Wy@LRgUeVhda;V2C(yRcd>0n1)*MuJ{SL6)yhK9{J4I(Q zSxq|laCow7=h;+aRCB@Adz1e9SD(u$bI%t*^&l<8J82hdKv5UCdGIE>5S?vR)d@4; zMKdrI;V@W?2iHFQB08&9%&j403e;%yo)H6n?(X{8GV_(?5rFI4R&5s5094OHzGM%U zHl2Cdg_3jg;8tN%Oc&wrE6e+h@M0GUBZdn#H;ukmI#0np@kyPC)ykU$T+21=_M;sl zP-vQ7nC-R&6&Hcav>JPjnVvT^hqHC7wQ{JJYuCrlJKtWGQ`fA?xreG=;Esc(kQzG; zdOZZ3<9eUs<4?)4cn6^f;=rnbLsEFt(wuKwTN>*bQwLEdbvyGU7IQoKB*4l>t2pa3 zzN5`C3v~H34*Ao&jp%vCk~>Ee89t^_68rghA9AMx?|TB8O|mIN1oq5#xwhKLEm&J= zH!$Uq7rZG41m&|iV4&9r-#A}Uet54hxoeK1uDuV}B}tMpCvxn7@xB;}Y7l(}a2QXk zWCJZZgo)==+)2-|I36l(Z5rSr!Wx)_n+;nQ4Hid$PfmL|c5+K>V9N^m6Px|f0`t@+ zz6JnOhCk%>IP4qp3Co1DUQVgd7S;#g@&a!Fz#`$nOOXa2X95?ddLBc?LR(#WY{I%a zVT}rma+gOxuR8l$)z8x>FfSK`d%;F6rrWkV9H&E&@85^T{`3VGFWP78Vi97rRMW9q z2a(6wWC~haZirHLj7|-21`j(rwu&kPJ54lV+%U+LzBI#%>AIo zz1;EM_{E_Nvf?cuQ~qczcY=|4oySm*mOzj%CF;K{5VXV#Y-|?n6()X<3}QO(s6vLg zdd8-Uh3HG*we!V-`n3>2h)Km`n+5^1V$1gTQ+j;o(+5R7YFam45aZdoSi8C20e*Hh zz^PliSQ}X^Jys-b^y)&syH^^)g?M==wC)kGndm1|o8IlKl2Kjba>+RfG*^aF>qC)S zi3=eka-Ieq3q4Px&I2%}+)5!Bi>N|4szWnH$F6dUxbm@EfK$GCokaY{J_&sr9CQlU z4TB9@;ju-A3eaYx(k%kFE{y&lCDtZS?4cVA zCsMZSkdabXUXx5^y3g=a#c&8XWK-a?iFc~E z=DK90m#+%f`{SGsb`G~oQ$jOq)cnMhJ~CU;{B*^TFI>oP#;*bS{*W#B3)cJqr#6q0 z7?s9S8A?!iZ;nVm0H9EHKJ!M3ydp z3Y)b@%zp*e1$cP`1*pl5Go7b7TP+bW6Fo&I2Du%m2W}t_Zh%{RZCw20gM|fNdScWR zxVVXg^7Cg5!uIE~$Rnb|f$$2lxuuY#vhgY-3|Y4&im{M>&{su zX+hRlCXc^MdQsprp5eOut#xNU%qUIF$>PRKRN z7o`799~FD58V7OoVOf z)b_7{Z)h=n4=6O(3G>}_A)%IUuG!t&5l(v8d0nW&8`7-}z@XBKEDd`5FWVNx;Uhd^ zv@l-p>a_$fTD_*c$8VB6T{MM@k)LTCo39VoF-g8yl`v2#A zX0sjSOK7=uGRN^8KO;p0Xb_b)SmI>1O7SETmfyKIN}Tv}A=Q)V9~D2?*cok8{6&3yZ zQwN<3j!@CUH&(fUAx^B(gkP6Fe>c&df3xwOTHqf%L=CVrAwaI0mZe3r3j#A=Y_adA z+x#OIT?CaL@j^f~?KV!i?3LwCea{Att-#&vl{vX43xWK)|G4`J|70qS=@MjlVJ+It ze6;O66N_ieJXWu9L7K>S_AOL$l)0^`di{iLbXAeN2|m|J(1pn@NZ7==C;uZCbWK

{4sp3#$8Y0 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 Date: Wed, 9 Mar 2016 09:14:58 -0500 Subject: [PATCH 022/147] Update package.json to match SwiftID --- package.json | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 7c49697..d45944b 100644 --- a/package.json +++ b/package.json @@ -1,16 +1,13 @@ { "name": "credit-offers", - "version": "0.0.1", - "private": true, + "version": "1.0.0", + "main": "./bin/www", "licenses": [ { "type": "Apache-2.0", "url": "http://www.apache.org/licenses/LICENSE-2.0" } ], - "scripts": { - "start": "node ./bin/www" - }, "dependencies": { "body-parser": "~1.13.2", "cookie-parser": "~1.3.5", @@ -24,5 +21,15 @@ "morgan": "~1.6.1", "request": "^2.69.0", "serve-favicon": "~2.3.0" + }, + "engines": { + "node": ">=4.0.0" + }, + "repository": { + "type": "git", + "url": "https://github.com/capitalone/CreditOffers-API-reference-app" + }, + "scripts": { + "start": "node ./bin/www" } } From 120ebf15d4d4ab2f6bc39fac60b2601d69b0c710 Mon Sep 17 00:00:00 2001 From: Santhi S Date: Wed, 9 Mar 2016 19:45:58 -0500 Subject: [PATCH 023/147] Include CLA and Open Source Code of Conduct Included the link to CLA and Open Source Code of COnduct --- README.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b127443..b8aa226 100644 --- a/README.md +++ b/README.md @@ -46,5 +46,15 @@ The application structure follows the pattern generated by the [Express applicat ## Roadmap This reference app code is intended as a starting place for developers who want to use the Credit Offers API. As such, it will be updated with new functionality only when the Credit Offers API is updated with new functionality. -## Contribution Guidelines +## Contributors +We welcome your interest in Capital One’s Open Source Projects (the “Project”). Any Contributor to the Project must accept and sign a CLA indicating agreement to the license terms. Except for the license granted in this CLA to Capital One and to recipients of software distributed by Capital One, You reserve all right, title, and interest in and to your Contributions; this CLA does not impact your rights to use your own contributions for any other purpose. + +##### [Link to Agreement] (https://docs.google.com/forms/d/19LpBBjykHPox18vrZvBbZUcK6gQTj7qv1O5hCduAZFU/viewform) + +This project adheres to the [Open Source Code of Conduct][code-of-conduct]. By participating, you are expected to honor this code. + +[code-of-conduct]: http://www.capitalone.io/codeofconduct/ + +### Contribution Guidelines We encourage any contributions that align with the intent of this project and add more functionality or languages that other developers can make use of. To contribute to the project, please submit a PR for our review. Before contributing any source code, familiarize yourself with the Apache License 2.0 (license.md), which controls the licensing for this project. + From 9f38b2082649d6da2d617b85361e2c9da0527da3 Mon Sep 17 00:00:00 2001 From: rkorsak Date: Mon, 28 Mar 2016 11:48:56 -0400 Subject: [PATCH 024/147] Reformat notices txt file --- credit-offers_notices.txt | 1220 ++++++++++++++++++------------------- 1 file changed, 610 insertions(+), 610 deletions(-) diff --git a/credit-offers_notices.txt b/credit-offers_notices.txt index 810bd43..ee95a3e 100644 --- a/credit-offers_notices.txt +++ b/credit-offers_notices.txt @@ -1,610 +1,610 @@ -├─ accepts@1.2.13 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/jshttp/accepts -├─ acorn-globals@1.0.9 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/ForbesLindesay/acorn-globals -├─ acorn@1.2.2 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/marijnh/acorn -├─ acorn@2.7.0 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/ternjs/acorn -├─ align-text@0.1.4 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/jonschlinkert/align-text -├─ amdefine@1.0.0 -│ ├─ licenses: BSD-3-Clause AND MIT -│ └─ repository: https://github.com/jrburke/amdefine -├─ ansi-regex@2.0.0 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/sindresorhus/ansi-regex -├─ ansi-styles@2.2.0 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/chalk/ansi-styles -├─ array-flatten@1.1.1 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/blakeembrey/array-flatten -├─ asap@1.0.0 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/kriskowal/asap -├─ asn1@0.2.3 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/mcavage/node-asn1 -├─ assert-plus@0.2.0 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/mcavage/node-assert-plus -├─ assert-plus@1.0.0 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/mcavage/node-assert-plus -├─ async@0.2.10 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/caolan/async -├─ async@1.5.2 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/caolan/async -├─ aws-sign2@0.6.0 -│ ├─ licenses: Apache-2.0 -│ └─ repository: https://github.com/mikeal/aws-sign -├─ aws4@1.3.1 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/mhart/aws4 -├─ base64-url@1.2.1 -│ ├─ licenses: ISC -│ └─ repository: https://github.com/joaquimserafim/base64-url -├─ basic-auth@1.0.3 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/jshttp/basic-auth -├─ bl@1.0.3 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/rvagg/bl -├─ body-parser@1.13.3 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/expressjs/body-parser -├─ boom@2.10.1 -│ ├─ licenses: BSD-3-Clause -│ └─ repository: https://github.com/hapijs/boom -├─ bytes@2.1.0 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/visionmedia/bytes.js -├─ bytes@2.2.0 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/visionmedia/bytes.js -├─ camelcase@1.2.1 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/sindresorhus/camelcase -├─ camelize@1.0.0 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/substack/camelize -├─ caseless@0.11.0 -│ ├─ licenses: Apache-2.0 -│ └─ repository: https://github.com/mikeal/caseless -├─ center-align@0.1.3 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/jonschlinkert/center-align -├─ chalk@1.1.1 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/chalk/chalk -├─ character-parser@1.2.1 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/ForbesLindesay/character-parser -├─ clean-css@3.4.10 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/jakubpawlowicz/clean-css -├─ cliui@2.1.0 -│ ├─ licenses: ISC -│ └─ repository: https://github.com/bcoe/cliui -├─ color-convert@1.0.0 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/qix-/color-convert -├─ combined-stream@1.0.5 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/felixge/node-combined-stream -├─ commander@2.6.0 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/tj/commander.js -├─ commander@2.8.1 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/tj/commander.js -├─ commander@2.9.0 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/tj/commander.js -├─ connect@3.4.1 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/senchalabs/connect -├─ constantinople@3.0.2 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/ForbesLindesay/constantinople -├─ content-disposition@0.5.1 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/jshttp/content-disposition -├─ content-security-policy-builder@1.0.0 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/helmetjs/content-security-policy-builder -├─ content-type@1.0.1 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/jshttp/content-type -├─ cookie-parser@1.3.5 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/expressjs/cookie-parser -├─ cookie-signature@1.0.6 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/visionmedia/node-cookie-signature -├─ cookie@0.1.3 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/jshttp/cookie -├─ cookie@0.1.5 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/jshttp/cookie -├─ core-util-is@1.0.2 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/isaacs/core-util-is -├─ credit-offers@0.0.1 -│ └─ licenses: Apache-2.0 -├─ cryptiles@2.0.5 -│ ├─ licenses: BSD-3-Clause -│ └─ repository: https://github.com/hapijs/cryptiles -├─ csrf@3.0.1 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/pillarjs/csrf -├─ css-parse@1.0.4 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/reworkcss/css-parse -├─ css-stringify@1.0.5 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/reworkcss/css-stringify -├─ css@1.0.8 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/reworkcss/css -├─ csurf@1.8.3 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/expressjs/csurf -├─ dashdash@1.13.0 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/trentm/node-dashdash -├─ dashify@0.2.1 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/jonschlinkert/dashify -├─ debug@2.2.0 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/visionmedia/debug -├─ decamelize@1.1.2 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/sindresorhus/decamelize -├─ delayed-stream@1.0.0 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/felixge/node-delayed-stream -├─ depd@1.0.1 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/dougwilson/nodejs-depd -├─ depd@1.1.0 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/dougwilson/nodejs-depd -├─ destroy@1.0.4 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/stream-utils/destroy -├─ dns-prefetch-control@0.1.0 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/helmetjs/dns-prefetch-control -├─ dont-sniff-mimetype@1.0.0 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/helmetjs/dont-sniff-mimetype -├─ ecc-jsbn@0.1.1 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/quartzjer/ecc-jsbn -├─ ee-first@1.1.1 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/jonathanong/ee-first -├─ ejs@2.3.4 -│ ├─ licenses: Apache-2.0 -│ └─ repository: https://github.com/mde/ejs -├─ escape-html@1.0.3 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/component/escape-html -├─ escape-string-regexp@1.0.5 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/sindresorhus/escape-string-regexp -├─ etag@1.7.0 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/jshttp/etag -├─ express@4.13.4 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/expressjs/express -├─ extend@3.0.0 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/justmoon/node-extend -├─ extsprintf@1.0.2 -│ ├─ licenses: MIT* -│ └─ repository: https://github.com/davepacheco/node-extsprintf -├─ finalhandler@0.4.1 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/pillarjs/finalhandler -├─ forever-agent@0.6.1 -│ ├─ licenses: Apache-2.0 -│ └─ repository: https://github.com/mikeal/forever-agent -├─ form-data@1.0.0-rc3 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/form-data/form-data -├─ forwarded@0.1.0 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/jshttp/forwarded -├─ frameguard@1.1.0 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/helmetjs/frameguard -├─ fresh@0.3.0 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/jshttp/fresh -├─ generate-function@2.0.0 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/mafintosh/generate-function -├─ generate-object-property@1.2.0 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/mafintosh/generate-object-property -├─ graceful-readlink@1.0.1 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/zhiyelee/graceful-readlink -├─ har-validator@2.0.6 -│ ├─ licenses: ISC -│ └─ repository: https://github.com/ahmadnassri/har-validator -├─ has-ansi@2.0.0 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/sindresorhus/has-ansi -├─ hawk@3.1.3 -│ ├─ licenses: BSD-3-Clause -│ └─ repository: https://github.com/hueniverse/hawk -├─ helmet-csp@1.1.0 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/helmetjs/csp -├─ helmet@1.2.0 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/helmetjs/helmet -├─ hide-powered-by@1.0.0 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/helmetjs/hide-powered-by -├─ hoek@2.16.3 -│ ├─ licenses: BSD-3-Clause -│ └─ repository: https://github.com/hapijs/hoek -├─ hpkp@1.0.0 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/helmetjs/hpkp -├─ hsts@1.0.0 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/helmetjs/hsts -├─ http-errors@1.3.1 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/jshttp/http-errors -├─ http-signature@1.1.1 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/joyent/node-http-signature -├─ iconv-lite@0.4.11 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/ashtuchkin/iconv-lite -├─ iconv-lite@0.4.13 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/ashtuchkin/iconv-lite -├─ ienoopen@1.0.0 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/helmetjs/ienoopen -├─ inherits@2.0.1 -│ ├─ licenses: ISC -│ └─ repository: https://github.com/isaacs/inherits -├─ ipaddr.js@1.0.5 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/whitequark/ipaddr.js -├─ is-buffer@1.1.2 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/feross/is-buffer -├─ is-my-json-valid@2.13.1 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/mafintosh/is-my-json-valid -├─ is-promise@1.0.1 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/then/is-promise -├─ is-promise@2.1.0 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/then/is-promise -├─ is-property@1.0.2 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/mikolalysenko/is-property -├─ is-typedarray@1.0.0 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/hughsk/is-typedarray -├─ isarray@0.0.1 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/juliangruber/isarray -├─ isstream@0.1.2 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/rvagg/isstream -├─ jade@1.11.0 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/jadejs/jade -├─ jodid25519@1.0.2 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/meganz/jodid25519 -├─ jsbn@0.1.0 -│ ├─ licenses: BSD -│ └─ repository: https://github.com/andyperlitch/jsbn -├─ json-schema@0.2.2 -│ ├─ licenses -│ │ ├─ 0: AFLv2.1 -│ │ └─ 1: BSD -│ └─ repository: https://github.com/kriszyp/json-schema -├─ json-stringify-safe@5.0.1 -│ ├─ licenses: ISC -│ └─ repository: https://github.com/isaacs/json-stringify-safe -├─ jsonpointer@2.0.0 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/janl/node-jsonpointer -├─ jsprim@1.2.2 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/davepacheco/node-jsprim -├─ jstransformer@0.0.2 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/jstransformers/jstransformer -├─ kind-of@3.0.2 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/jonschlinkert/kind-of -├─ lazy-cache@1.0.3 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/jonschlinkert/lazy-cache -├─ lodash._baseeach@4.1.0 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/lodash/lodash -├─ lodash._baseiteratee@4.5.1 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/lodash/lodash -├─ lodash._basereduce@3.0.2 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/lodash/lodash -├─ lodash.assign@4.0.4 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/lodash/lodash -├─ lodash.isfunction@3.0.8 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/lodash/lodash -├─ lodash.isstring@4.0.1 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/lodash/lodash -├─ lodash.keys@4.0.4 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/lodash/lodash -├─ lodash.reduce@4.2.0 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/lodash/lodash -├─ lodash.rest@4.0.1 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/lodash/lodash -├─ lodash.some@4.2.0 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/lodash/lodash -├─ lodash@4.5.1 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/lodash/lodash -├─ longest@1.0.1 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/jonschlinkert/longest -├─ lru-cache@4.0.0 -│ ├─ licenses: ISC -│ └─ repository: https://github.com/isaacs/node-lru-cache -├─ media-typer@0.3.0 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/jshttp/media-typer -├─ merge-descriptors@1.0.1 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/component/merge-descriptors -├─ methods@1.1.2 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/jshttp/methods -├─ mime-db@1.22.0 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/jshttp/mime-db -├─ mime-types@2.1.10 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/jshttp/mime-types -├─ mime@1.3.4 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/broofa/node-mime -├─ minimist@0.0.8 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/substack/minimist -├─ mkdirp@0.5.1 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/substack/node-mkdirp -├─ morgan@1.6.1 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/expressjs/morgan -├─ ms@0.7.1 -│ ├─ licenses: MIT* -│ └─ repository: https://github.com/guille/ms.js -├─ negotiator@0.5.3 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/jshttp/negotiator -├─ nocache@1.0.0 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/helmetjs/nocache -├─ node-uuid@1.4.7 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/broofa/node-uuid -├─ oauth-sign@0.8.1 -│ ├─ licenses: Apache-2.0 -│ └─ repository: https://github.com/mikeal/oauth-sign -├─ on-finished@2.3.0 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/jshttp/on-finished -├─ on-headers@1.0.1 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/jshttp/on-headers -├─ optimist@0.3.7 -│ ├─ licenses: MIT/X11 -│ └─ repository: https://github.com/substack/node-optimist -├─ parseurl@1.3.1 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/pillarjs/parseurl -├─ path-to-regexp@0.1.7 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/component/path-to-regexp -├─ pinkie-promise@2.0.0 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/floatdrop/pinkie-promise -├─ pinkie@2.0.4 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/floatdrop/pinkie -├─ platform@1.3.1 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/bestiejs/platform.js -├─ process-nextick-args@1.0.6 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/calvinmetcalf/process-nextick-args -├─ promise@2.0.0 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/then/promise -├─ promise@6.1.0 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/then/promise -├─ proxy-addr@1.0.10 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/jshttp/proxy-addr -├─ pseudomap@1.0.2 -│ ├─ licenses: ISC -│ └─ repository: https://github.com/isaacs/pseudomap -├─ qs@4.0.0 -│ ├─ licenses: BSD-3-Clause -│ └─ repository: https://github.com/hapijs/qs -├─ qs@6.0.2 -│ ├─ licenses: BSD-3-Clause -│ └─ repository: https://github.com/ljharb/qs -├─ random-bytes@1.0.0 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/crypto-utils/random-bytes -├─ range-parser@1.0.3 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/jshttp/range-parser -├─ raw-body@2.1.5 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/stream-utils/raw-body -├─ readable-stream@2.0.5 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/nodejs/readable-stream -├─ repeat-string@1.5.4 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/jonschlinkert/repeat-string -├─ request@2.69.0 -│ ├─ licenses: Apache-2.0 -│ └─ repository: https://github.com/request/request -├─ right-align@0.1.3 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/jonschlinkert/right-align -├─ rndm@1.2.0 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/crypto-utils/rndm -├─ scmp@1.0.0 -│ ├─ licenses: BSD -│ └─ repository: https://github.com/freewil/scmp -├─ send@0.13.1 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/pillarjs/send -├─ serve-favicon@2.3.0 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/expressjs/serve-favicon -├─ serve-static@1.10.2 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/expressjs/serve-static -├─ sntp@1.0.9 -│ ├─ licenses: BSD -│ └─ repository: https://github.com/hueniverse/sntp -├─ source-map@0.1.43 -│ ├─ licenses: BSD -│ └─ repository: https://github.com/mozilla/source-map -├─ source-map@0.4.4 -│ ├─ licenses: BSD-3-Clause -│ └─ repository: https://github.com/mozilla/source-map -├─ source-map@0.5.3 -│ ├─ licenses: BSD-3-Clause -│ └─ repository: https://github.com/mozilla/source-map -├─ sshpk@1.7.4 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/arekinath/node-sshpk -├─ statuses@1.2.1 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/jshttp/statuses -├─ string_decoder@0.10.31 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/rvagg/string_decoder -├─ stringstream@0.0.5 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/mhart/StringStream -├─ strip-ansi@3.0.1 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/chalk/strip-ansi -├─ supports-color@2.0.0 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/chalk/supports-color -├─ tough-cookie@2.2.1 -│ ├─ licenses: BSD-3-Clause -│ └─ repository: https://github.com/SalesforceEng/tough-cookie -├─ transformers@2.1.0 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/ForbesLindesay/transformers -├─ tunnel-agent@0.4.2 -│ ├─ licenses: Apache-2.0 -│ └─ repository: https://github.com/mikeal/tunnel-agent -├─ tweetnacl@0.14.1 -│ ├─ licenses: Public Domain -│ └─ repository: https://github.com/dchest/tweetnacl-js -├─ type-is@1.6.12 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/jshttp/type-is -├─ uglify-js@2.2.5 -│ ├─ licenses: BSD -│ └─ repository: https://github.com/mishoo/UglifyJS2 -├─ uglify-js@2.6.2 -│ ├─ licenses: BSD-2-Clause -│ └─ repository: https://github.com/mishoo/UglifyJS2 -├─ uglify-to-browserify@1.0.2 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/ForbesLindesay/uglify-to-browserify -├─ uid-safe@2.1.0 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/crypto-utils/uid-safe -├─ unpipe@1.0.0 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/stream-utils/unpipe -├─ util-deprecate@1.0.2 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/TooTallNate/util-deprecate -├─ utils-merge@1.0.0 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/jaredhanson/utils-merge -├─ vary@1.0.1 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/jshttp/vary -├─ verror@1.3.6 -│ ├─ licenses: MIT* -│ └─ repository: https://github.com/davepacheco/node-verror -├─ void-elements@2.0.1 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/hemanth/void-elements -├─ window-size@0.1.0 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/jonschlinkert/window-size -├─ with@4.0.3 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/ForbesLindesay/with -├─ wordwrap@0.0.2 -│ ├─ licenses: MIT/X11 -│ └─ repository: https://github.com/substack/node-wordwrap -├─ wordwrap@0.0.3 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/substack/node-wordwrap -├─ x-xss-protection@1.0.0 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/helmetjs/x-xss-protection -├─ xtend@4.0.1 -│ ├─ licenses: MIT -│ └─ repository: https://github.com/Raynos/xtend -├─ yallist@2.0.0 -│ ├─ licenses: ISC -│ └─ repository: https://github.com/isaacs/yallist -└─ yargs@3.10.0 - ├─ licenses: MIT - └─ repository: https://github.com/bcoe/yargs +accepts@1.2.13 + licenses: MIT + repository: https://github.com/jshttp/accepts +acorn-globals@1.0.9 + licenses: MIT + repository: https://github.com/ForbesLindesay/acorn-globals +acorn@1.2.2 + licenses: MIT + repository: https://github.com/marijnh/acorn +acorn@2.7.0 + licenses: MIT + repository: https://github.com/ternjs/acorn +align-text@0.1.4 + licenses: MIT + repository: https://github.com/jonschlinkert/align-text +amdefine@1.0.0 + licenses: BSD-3-Clause AND MIT + repository: https://github.com/jrburke/amdefine +ansi-regex@2.0.0 + licenses: MIT + repository: https://github.com/sindresorhus/ansi-regex +ansi-styles@2.2.0 + licenses: MIT + repository: https://github.com/chalk/ansi-styles +array-flatten@1.1.1 + licenses: MIT + repository: https://github.com/blakeembrey/array-flatten +asap@1.0.0 + licenses: MIT + repository: https://github.com/kriskowal/asap +asn1@0.2.3 + licenses: MIT + repository: https://github.com/mcavage/node-asn1 +assert-plus@0.2.0 + licenses: MIT + repository: https://github.com/mcavage/node-assert-plus +assert-plus@1.0.0 + licenses: MIT + repository: https://github.com/mcavage/node-assert-plus +async@0.2.10 + licenses: MIT + repository: https://github.com/caolan/async +async@1.5.2 + licenses: MIT + repository: https://github.com/caolan/async +aws-sign2@0.6.0 + licenses: Apache-2.0 + repository: https://github.com/mikeal/aws-sign +aws4@1.3.1 + licenses: MIT + repository: https://github.com/mhart/aws4 +base64-url@1.2.1 + licenses: ISC + repository: https://github.com/joaquimserafim/base64-url +basic-auth@1.0.3 + licenses: MIT + repository: https://github.com/jshttp/basic-auth +bl@1.0.3 + licenses: MIT + repository: https://github.com/rvagg/bl +body-parser@1.13.3 + licenses: MIT + repository: https://github.com/expressjs/body-parser +boom@2.10.1 + licenses: BSD-3-Clause + repository: https://github.com/hapijs/boom +bytes@2.1.0 + licenses: MIT + repository: https://github.com/visionmedia/bytes.js +bytes@2.2.0 + licenses: MIT + repository: https://github.com/visionmedia/bytes.js +camelcase@1.2.1 + licenses: MIT + repository: https://github.com/sindresorhus/camelcase +camelize@1.0.0 + licenses: MIT + repository: https://github.com/substack/camelize +caseless@0.11.0 + licenses: Apache-2.0 + repository: https://github.com/mikeal/caseless +center-align@0.1.3 + licenses: MIT + repository: https://github.com/jonschlinkert/center-align +chalk@1.1.1 + licenses: MIT + repository: https://github.com/chalk/chalk +character-parser@1.2.1 + licenses: MIT + repository: https://github.com/ForbesLindesay/character-parser +clean-css@3.4.10 + licenses: MIT + repository: https://github.com/jakubpawlowicz/clean-css +cliui@2.1.0 + licenses: ISC + repository: https://github.com/bcoe/cliui +color-convert@1.0.0 + licenses: MIT + repository: https://github.com/qix-/color-convert +combined-stream@1.0.5 + licenses: MIT + repository: https://github.com/felixge/node-combined-stream +commander@2.6.0 + licenses: MIT + repository: https://github.com/tj/commander.js +commander@2.8.1 + licenses: MIT + repository: https://github.com/tj/commander.js +commander@2.9.0 + licenses: MIT + repository: https://github.com/tj/commander.js +connect@3.4.1 + licenses: MIT + repository: https://github.com/senchalabs/connect +constantinople@3.0.2 + licenses: MIT + repository: https://github.com/ForbesLindesay/constantinople +content-disposition@0.5.1 + licenses: MIT + repository: https://github.com/jshttp/content-disposition +content-security-policy-builder@1.0.0 + licenses: MIT + repository: https://github.com/helmetjs/content-security-policy-builder +content-type@1.0.1 + licenses: MIT + repository: https://github.com/jshttp/content-type +cookie-parser@1.3.5 + licenses: MIT + repository: https://github.com/expressjs/cookie-parser +cookie-signature@1.0.6 + licenses: MIT + repository: https://github.com/visionmedia/node-cookie-signature +cookie@0.1.3 + licenses: MIT + repository: https://github.com/jshttp/cookie +cookie@0.1.5 + licenses: MIT + repository: https://github.com/jshttp/cookie +core-util-is@1.0.2 + licenses: MIT + repository: https://github.com/isaacs/core-util-is +credit-offers@0.0.1 + licenses: Apache-2.0 +cryptiles@2.0.5 + licenses: BSD-3-Clause + repository: https://github.com/hapijs/cryptiles +csrf@3.0.1 + licenses: MIT + repository: https://github.com/pillarjs/csrf +css-parse@1.0.4 + licenses: MIT + repository: https://github.com/reworkcss/css-parse +css-stringify@1.0.5 + licenses: MIT + repository: https://github.com/reworkcss/css-stringify +css@1.0.8 + licenses: MIT + repository: https://github.com/reworkcss/css +csurf@1.8.3 + licenses: MIT + repository: https://github.com/expressjs/csurf +dashdash@1.13.0 + licenses: MIT + repository: https://github.com/trentm/node-dashdash +dashify@0.2.1 + licenses: MIT + repository: https://github.com/jonschlinkert/dashify +debug@2.2.0 + licenses: MIT + repository: https://github.com/visionmedia/debug +decamelize@1.1.2 + licenses: MIT + repository: https://github.com/sindresorhus/decamelize +delayed-stream@1.0.0 + licenses: MIT + repository: https://github.com/felixge/node-delayed-stream +depd@1.0.1 + licenses: MIT + repository: https://github.com/dougwilson/nodejs-depd +depd@1.1.0 + licenses: MIT + repository: https://github.com/dougwilson/nodejs-depd +destroy@1.0.4 + licenses: MIT + repository: https://github.com/stream-utils/destroy +dns-prefetch-control@0.1.0 + licenses: MIT + repository: https://github.com/helmetjs/dns-prefetch-control +dont-sniff-mimetype@1.0.0 + licenses: MIT + repository: https://github.com/helmetjs/dont-sniff-mimetype +ecc-jsbn@0.1.1 + licenses: MIT + repository: https://github.com/quartzjer/ecc-jsbn +ee-first@1.1.1 + licenses: MIT + repository: https://github.com/jonathanong/ee-first +ejs@2.3.4 + licenses: Apache-2.0 + repository: https://github.com/mde/ejs +escape-html@1.0.3 + licenses: MIT + repository: https://github.com/component/escape-html +escape-string-regexp@1.0.5 + licenses: MIT + repository: https://github.com/sindresorhus/escape-string-regexp +etag@1.7.0 + licenses: MIT + repository: https://github.com/jshttp/etag +express@4.13.4 + licenses: MIT + repository: https://github.com/expressjs/express +extend@3.0.0 + licenses: MIT + repository: https://github.com/justmoon/node-extend +extsprintf@1.0.2 + licenses: MIT* + repository: https://github.com/davepacheco/node-extsprintf +finalhandler@0.4.1 + licenses: MIT + repository: https://github.com/pillarjs/finalhandler +forever-agent@0.6.1 + licenses: Apache-2.0 + repository: https://github.com/mikeal/forever-agent +form-data@1.0.0-rc3 + licenses: MIT + repository: https://github.com/form-data/form-data +forwarded@0.1.0 + licenses: MIT + repository: https://github.com/jshttp/forwarded +frameguard@1.1.0 + licenses: MIT + repository: https://github.com/helmetjs/frameguard +fresh@0.3.0 + licenses: MIT + repository: https://github.com/jshttp/fresh +generate-function@2.0.0 + licenses: MIT + repository: https://github.com/mafintosh/generate-function +generate-object-property@1.2.0 + licenses: MIT + repository: https://github.com/mafintosh/generate-object-property +graceful-readlink@1.0.1 + licenses: MIT + repository: https://github.com/zhiyelee/graceful-readlink +har-validator@2.0.6 + licenses: ISC + repository: https://github.com/ahmadnassri/har-validator +has-ansi@2.0.0 + licenses: MIT + repository: https://github.com/sindresorhus/has-ansi +hawk@3.1.3 + licenses: BSD-3-Clause + repository: https://github.com/hueniverse/hawk +helmet-csp@1.1.0 + licenses: MIT + repository: https://github.com/helmetjs/csp +helmet@1.2.0 + licenses: MIT + repository: https://github.com/helmetjs/helmet +hide-powered-by@1.0.0 + licenses: MIT + repository: https://github.com/helmetjs/hide-powered-by +hoek@2.16.3 + licenses: BSD-3-Clause + repository: https://github.com/hapijs/hoek +hpkp@1.0.0 + licenses: MIT + repository: https://github.com/helmetjs/hpkp +hsts@1.0.0 + licenses: MIT + repository: https://github.com/helmetjs/hsts +http-errors@1.3.1 + licenses: MIT + repository: https://github.com/jshttp/http-errors +http-signature@1.1.1 + licenses: MIT + repository: https://github.com/joyent/node-http-signature +iconv-lite@0.4.11 + licenses: MIT + repository: https://github.com/ashtuchkin/iconv-lite +iconv-lite@0.4.13 + licenses: MIT + repository: https://github.com/ashtuchkin/iconv-lite +ienoopen@1.0.0 + licenses: MIT + repository: https://github.com/helmetjs/ienoopen +inherits@2.0.1 + licenses: ISC + repository: https://github.com/isaacs/inherits +ipaddr.js@1.0.5 + licenses: MIT + repository: https://github.com/whitequark/ipaddr.js +is-buffer@1.1.2 + licenses: MIT + repository: https://github.com/feross/is-buffer +is-my-json-valid@2.13.1 + licenses: MIT + repository: https://github.com/mafintosh/is-my-json-valid +is-promise@1.0.1 + licenses: MIT + repository: https://github.com/then/is-promise +is-promise@2.1.0 + licenses: MIT + repository: https://github.com/then/is-promise +is-property@1.0.2 + licenses: MIT + repository: https://github.com/mikolalysenko/is-property +is-typedarray@1.0.0 + licenses: MIT + repository: https://github.com/hughsk/is-typedarray +isarray@0.0.1 + licenses: MIT + repository: https://github.com/juliangruber/isarray +isstream@0.1.2 + licenses: MIT + repository: https://github.com/rvagg/isstream +jade@1.11.0 + licenses: MIT + repository: https://github.com/jadejs/jade +jodid25519@1.0.2 + licenses: MIT + repository: https://github.com/meganz/jodid25519 +jsbn@0.1.0 + licenses: BSD + repository: https://github.com/andyperlitch/jsbn +json-schema@0.2.2 + licenses + - AFLv2.1 + - BSD + repository: https://github.com/kriszyp/json-schema +json-stringify-safe@5.0.1 + licenses: ISC + repository: https://github.com/isaacs/json-stringify-safe +jsonpointer@2.0.0 + licenses: MIT + repository: https://github.com/janl/node-jsonpointer +jsprim@1.2.2 + licenses: MIT + repository: https://github.com/davepacheco/node-jsprim +jstransformer@0.0.2 + licenses: MIT + repository: https://github.com/jstransformers/jstransformer +kind-of@3.0.2 + licenses: MIT + repository: https://github.com/jonschlinkert/kind-of +lazy-cache@1.0.3 + licenses: MIT + repository: https://github.com/jonschlinkert/lazy-cache +lodash._baseeach@4.1.0 + licenses: MIT + repository: https://github.com/lodash/lodash +lodash._baseiteratee@4.5.1 + licenses: MIT + repository: https://github.com/lodash/lodash +lodash._basereduce@3.0.2 + licenses: MIT + repository: https://github.com/lodash/lodash +lodash.assign@4.0.4 + licenses: MIT + repository: https://github.com/lodash/lodash +lodash.isfunction@3.0.8 + licenses: MIT + repository: https://github.com/lodash/lodash +lodash.isstring@4.0.1 + licenses: MIT + repository: https://github.com/lodash/lodash +lodash.keys@4.0.4 + licenses: MIT + repository: https://github.com/lodash/lodash +lodash.reduce@4.2.0 + licenses: MIT + repository: https://github.com/lodash/lodash +lodash.rest@4.0.1 + licenses: MIT + repository: https://github.com/lodash/lodash +lodash.some@4.2.0 + licenses: MIT + repository: https://github.com/lodash/lodash +lodash@4.5.1 + licenses: MIT + repository: https://github.com/lodash/lodash +longest@1.0.1 + licenses: MIT + repository: https://github.com/jonschlinkert/longest +lru-cache@4.0.0 + licenses: ISC + repository: https://github.com/isaacs/node-lru-cache +media-typer@0.3.0 + licenses: MIT + repository: https://github.com/jshttp/media-typer +merge-descriptors@1.0.1 + licenses: MIT + repository: https://github.com/component/merge-descriptors +methods@1.1.2 + licenses: MIT + repository: https://github.com/jshttp/methods +mime-db@1.22.0 + licenses: MIT + repository: https://github.com/jshttp/mime-db +mime-types@2.1.10 + licenses: MIT + repository: https://github.com/jshttp/mime-types +mime@1.3.4 + licenses: MIT + repository: https://github.com/broofa/node-mime +minimist@0.0.8 + licenses: MIT + repository: https://github.com/substack/minimist +mkdirp@0.5.1 + licenses: MIT + repository: https://github.com/substack/node-mkdirp +morgan@1.6.1 + licenses: MIT + repository: https://github.com/expressjs/morgan +ms@0.7.1 + licenses: MIT* + repository: https://github.com/guille/ms.js +negotiator@0.5.3 + licenses: MIT + repository: https://github.com/jshttp/negotiator +nocache@1.0.0 + licenses: MIT + repository: https://github.com/helmetjs/nocache +node-uuid@1.4.7 + licenses: MIT + repository: https://github.com/broofa/node-uuid +oauth-sign@0.8.1 + licenses: Apache-2.0 + repository: https://github.com/mikeal/oauth-sign +on-finished@2.3.0 + licenses: MIT + repository: https://github.com/jshttp/on-finished +on-headers@1.0.1 + licenses: MIT + repository: https://github.com/jshttp/on-headers +optimist@0.3.7 + licenses: MIT/X11 + repository: https://github.com/substack/node-optimist +parseurl@1.3.1 + licenses: MIT + repository: https://github.com/pillarjs/parseurl +path-to-regexp@0.1.7 + licenses: MIT + repository: https://github.com/component/path-to-regexp +pinkie-promise@2.0.0 + licenses: MIT + repository: https://github.com/floatdrop/pinkie-promise +pinkie@2.0.4 + licenses: MIT + repository: https://github.com/floatdrop/pinkie +platform@1.3.1 + licenses: MIT + repository: https://github.com/bestiejs/platform.js +process-nextick-args@1.0.6 + licenses: MIT + repository: https://github.com/calvinmetcalf/process-nextick-args +promise@2.0.0 + licenses: MIT + repository: https://github.com/then/promise +promise@6.1.0 + licenses: MIT + repository: https://github.com/then/promise +proxy-addr@1.0.10 + licenses: MIT + repository: https://github.com/jshttp/proxy-addr +pseudomap@1.0.2 + licenses: ISC + repository: https://github.com/isaacs/pseudomap +qs@4.0.0 + licenses: BSD-3-Clause + repository: https://github.com/hapijs/qs +qs@6.0.2 + licenses: BSD-3-Clause + repository: https://github.com/ljharb/qs +random-bytes@1.0.0 + licenses: MIT + repository: https://github.com/crypto-utils/random-bytes +range-parser@1.0.3 + licenses: MIT + repository: https://github.com/jshttp/range-parser +raw-body@2.1.5 + licenses: MIT + repository: https://github.com/stream-utils/raw-body +readable-stream@2.0.5 + licenses: MIT + repository: https://github.com/nodejs/readable-stream +repeat-string@1.5.4 + licenses: MIT + repository: https://github.com/jonschlinkert/repeat-string +request@2.69.0 + licenses: Apache-2.0 + repository: https://github.com/request/request +right-align@0.1.3 + licenses: MIT + repository: https://github.com/jonschlinkert/right-align +rndm@1.2.0 + licenses: MIT + repository: https://github.com/crypto-utils/rndm +scmp@1.0.0 + licenses: BSD + repository: https://github.com/freewil/scmp +send@0.13.1 + licenses: MIT + repository: https://github.com/pillarjs/send +serve-favicon@2.3.0 + licenses: MIT + repository: https://github.com/expressjs/serve-favicon +serve-static@1.10.2 + licenses: MIT + repository: https://github.com/expressjs/serve-static +sntp@1.0.9 + licenses: BSD + repository: https://github.com/hueniverse/sntp +source-map@0.1.43 + licenses: BSD + repository: https://github.com/mozilla/source-map +source-map@0.4.4 + licenses: BSD-3-Clause + repository: https://github.com/mozilla/source-map +source-map@0.5.3 + licenses: BSD-3-Clause + repository: https://github.com/mozilla/source-map +sshpk@1.7.4 + licenses: MIT + repository: https://github.com/arekinath/node-sshpk +statuses@1.2.1 + licenses: MIT + repository: https://github.com/jshttp/statuses +string_decoder@0.10.31 + licenses: MIT + repository: https://github.com/rvagg/string_decoder +stringstream@0.0.5 + licenses: MIT + repository: https://github.com/mhart/StringStream +strip-ansi@3.0.1 + licenses: MIT + repository: https://github.com/chalk/strip-ansi +supports-color@2.0.0 + licenses: MIT + repository: https://github.com/chalk/supports-color +tough-cookie@2.2.1 + licenses: BSD-3-Clause + repository: https://github.com/SalesforceEng/tough-cookie +transformers@2.1.0 + licenses: MIT + repository: https://github.com/ForbesLindesay/transformers +tunnel-agent@0.4.2 + licenses: Apache-2.0 + repository: https://github.com/mikeal/tunnel-agent +tweetnacl@0.14.1 + licenses: Public Domain + repository: https://github.com/dchest/tweetnacl-js +type-is@1.6.12 + licenses: MIT + repository: https://github.com/jshttp/type-is +uglify-js@2.2.5 + licenses: BSD + repository: https://github.com/mishoo/UglifyJS2 +uglify-js@2.6.2 + licenses: BSD-2-Clause + repository: https://github.com/mishoo/UglifyJS2 +uglify-to-browserify@1.0.2 + licenses: MIT + repository: https://github.com/ForbesLindesay/uglify-to-browserify +uid-safe@2.1.0 + licenses: MIT + repository: https://github.com/crypto-utils/uid-safe +unpipe@1.0.0 + licenses: MIT + repository: https://github.com/stream-utils/unpipe +util-deprecate@1.0.2 + licenses: MIT + repository: https://github.com/TooTallNate/util-deprecate +utils-merge@1.0.0 + licenses: MIT + repository: https://github.com/jaredhanson/utils-merge +vary@1.0.1 + licenses: MIT + repository: https://github.com/jshttp/vary +verror@1.3.6 + licenses: MIT* + repository: https://github.com/davepacheco/node-verror +void-elements@2.0.1 + licenses: MIT + repository: https://github.com/hemanth/void-elements +window-size@0.1.0 + licenses: MIT + repository: https://github.com/jonschlinkert/window-size +with@4.0.3 + licenses: MIT + repository: https://github.com/ForbesLindesay/with +wordwrap@0.0.2 + licenses: MIT/X11 + repository: https://github.com/substack/node-wordwrap +wordwrap@0.0.3 + licenses: MIT + repository: https://github.com/substack/node-wordwrap +x-xss-protection@1.0.0 + licenses: MIT + repository: https://github.com/helmetjs/x-xss-protection +xtend@4.0.1 + licenses: MIT + repository: https://github.com/Raynos/xtend +yallist@2.0.0 + licenses: ISC + repository: https://github.com/isaacs/yallist +yargs@3.10.0 + licenses: MIT + repository: https://github.com/bcoe/yargs From 76f31fd58a3cc0eec11fb267f79651b3d895a491 Mon Sep 17 00:00:00 2001 From: rkorsak Date: Mon, 4 Apr 2016 13:52:01 -0400 Subject: [PATCH 025/147] Use more restrictive version matching in package.json --- package.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index d45944b..d8acb2d 100644 --- a/package.json +++ b/package.json @@ -11,15 +11,15 @@ "dependencies": { "body-parser": "~1.13.2", "cookie-parser": "~1.3.5", - "csurf": "^1.8.3", + "csurf": "~1.8.3", "debug": "~2.2.0", "ejs": "~2.3.3", "express": "~4.13.1", - "helmet": "^1.1.0", - "jade": "^1.11.0", - "lodash": "^4.1.0", + "helmet": "~1.1.0", + "jade": "~1.11.0", + "lodash": "~4.1.0", "morgan": "~1.6.1", - "request": "^2.69.0", + "request": "~2.69.0", "serve-favicon": "~2.3.0" }, "engines": { From 9bfe7e6724e23d587d52ffb67c32eb1ea85391e5 Mon Sep 17 00:00:00 2001 From: Jennifer Rondeau Date: Fri, 14 Oct 2016 15:02:54 -0400 Subject: [PATCH 026/147] fix UI text per affiliates requirements Also edited for clarity in customer form. --- views/includes/customer-form.jade | 2 +- views/offers.jade | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/views/includes/customer-form.jade b/views/includes/customer-form.jade index 8c7236c..ad372d5 100644 --- a/views/includes/customer-form.jade +++ b/views/includes/customer-form.jade @@ -101,7 +101,7 @@ mixin stateOptions() +textinput('taxId', 'Last Four Digits of SSN', true) +textinput('dateOfBirth', 'Birth Date', false)(placeholder='YYYY-MM-DD') -div.form-note The below information is optional but can be used to fine tune the offers returned +div.form-note The following information is optional. However, it can help us show you offers that might match your needs and circumstances better. +textinput('emailAddress', 'Email Address') +select('primaryBenefit', 'Most Desired Credit Card Benefit') option(value='NotSure') Not sure diff --git a/views/offers.jade b/views/offers.jade index a2f91b9..afcf73f 100644 --- a/views/offers.jade +++ b/views/offers.jade @@ -18,9 +18,9 @@ block content if error span.error= error else if isPrequalified - h1 Good news! You already qualify! + h1 You prequalify for the following offers: else if products && products.length - h1 Check out these great offers! + h1 We're sorry, you don't prequalify, but you can check out these great offers: else h1 We weren't able to find any offers From bfad582a0cbea68aa5a67e7203159e7676c6f3cd Mon Sep 17 00:00:00 2001 From: Jennifer Rondeau Date: Fri, 14 Oct 2016 15:09:41 -0400 Subject: [PATCH 027/147] fix UI text better --- views/offers.jade | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/views/offers.jade b/views/offers.jade index afcf73f..f371d96 100644 --- a/views/offers.jade +++ b/views/offers.jade @@ -20,7 +20,7 @@ block content else if isPrequalified h1 You prequalify for the following offers: else if products && products.length - h1 We're sorry, you don't prequalify, but you can check out these great offers: + h1 We're sorry, we can't prequalify you based on the information that you provided, but you can check out these great offers: else h1 We weren't able to find any offers From 6aad235a8ca62b0cfbc5d2724fe0545744a067e3 Mon Sep 17 00:00:00 2001 From: rkorsak Date: Tue, 18 Oct 2016 11:52:56 -0400 Subject: [PATCH 028/147] Updated package.json to 2.0.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d8acb2d..f77d2ff 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "credit-offers", - "version": "1.0.0", + "version": "2.0.0", "main": "./bin/www", "licenses": [ { From 7a5792eb2c7654d392ffff4b93163aa625396a5b Mon Sep 17 00:00:00 2001 From: rkorsak Date: Tue, 18 Oct 2016 12:11:02 -0400 Subject: [PATCH 029/147] Re-added mock API --- mock_api/app.js | 56 +++++++ mock_api/bin/www | 90 +++++++++++ mock_api/package.json | 17 +++ ...eric-VentureOne-EMV-flat-244x154-06-15.png | Bin 0 -> 23386 bytes ...ericEMV-Venture-EMV-flat-244x154-06-15.png | Bin 0 -> 21775 bytes ...0-MC-Blue-Steel-EMV-flat-244x154-06-15.png | Bin 0 -> 16768 bytes .../images/www-venture-visa-sig-flat-9-14.png | Bin 0 -> 21550 bytes mock_api/routes/index.js | 141 ++++++++++++++++++ mock_api/routes/oauth.js | 72 +++++++++ mock_api/views/authorize.jade | 9 ++ mock_api/views/error.jade | 19 +++ mock_api/views/layout.jade | 21 +++ 12 files changed, 425 insertions(+) create mode 100644 mock_api/app.js create mode 100644 mock_api/bin/www create mode 100644 mock_api/package.json create mode 100644 mock_api/public/images/JB16760-Generic-VentureOne-EMV-flat-244x154-06-15.png create mode 100644 mock_api/public/images/JB16760-GenericEMV-Venture-EMV-flat-244x154-06-15.png create mode 100644 mock_api/public/images/JB16760-MC-Blue-Steel-EMV-flat-244x154-06-15.png create mode 100644 mock_api/public/images/www-venture-visa-sig-flat-9-14.png create mode 100644 mock_api/routes/index.js create mode 100644 mock_api/routes/oauth.js create mode 100644 mock_api/views/authorize.jade create mode 100644 mock_api/views/error.jade create mode 100644 mock_api/views/layout.jade diff --git a/mock_api/app.js b/mock_api/app.js new file mode 100644 index 0000000..b8a6210 --- /dev/null +++ b/mock_api/app.js @@ -0,0 +1,56 @@ +var express = require('express') +var path = require('path') +var logger = require('morgan') +var cookieParser = require('cookie-parser') +var bodyParser = require('body-parser') + +var routes = require('./routes/index') +var oauth = require('./routes/oauth') + +var app = express() + +// view engine setup +app.set('views', path.join(__dirname, 'views')) +app.set('view engine', 'jade') + +app.use(logger('dev')) +app.use(bodyParser.json()) +app.use(bodyParser.urlencoded({ extended: false })) +app.use(cookieParser()) +app.use(express.static(path.join(__dirname, 'public'))) + +app.use('/', routes) +app.use('/oauth/', oauth) + +// catch 404 and forward to error handler +app.use(function (req, res, next) { + var err = new Error('Not Found') + err.status = 404 + next(err) +}) + +// error handlers + +// development error handler +// will print stacktrace +if (app.get('env') === 'development') { + app.use(function (err, req, res, next) { + res.status(err.status || 500) + res.render('error', { + message: err.message, + error: err + }) + }) +} + +// production error handler +// no stacktraces leaked to user +app.use(function (err, req, res, next) { + res.status(err.status || 500) + res.render('error', { + message: err.message, + error: {} + }) +}) + +module.exports = app diff --git a/mock_api/bin/www b/mock_api/bin/www new file mode 100644 index 0000000..ec0dad6 --- /dev/null +++ b/mock_api/bin/www @@ -0,0 +1,90 @@ +#!/usr/bin/env node + +/** + * Module dependencies. + */ + +var app = require('../app') +var debug = require('debug')('credit-offers_mock_api:server') +var http = require('http') + +/** + * Get port from environment and store in Express. + */ + +var port = normalizePort(process.env.PORT || '3002') +app.set('port', port) + +/** + * Create HTTP server. + */ + +var server = http.createServer(app) + +/** + * Listen on provided port, on all network interfaces. + */ + +server.listen(port) +server.on('error', onError) +server.on('listening', onListening) + +/** + * Normalize a port into a number, string, or false. + */ + +function normalizePort (val) { + var port = parseInt(val, 10) + + if (isNaN(port)) { + // named pipe + return val + } + + if (port >= 0) { + // port number + return port + } + + return false +} + +/** + * Event listener for HTTP server "error" event. + */ + +function onError (error) { + if (error.syscall !== 'listen') { + throw error + } + + var bind = typeof port === 'string' + ? 'Pipe ' + port + : 'Port ' + port + + // handle specific listen errors with friendly messages + switch (error.code) { + case 'EACCES': + console.error(bind + ' requires elevated privileges') + process.exit(1) + break + case 'EADDRINUSE': + console.error(bind + ' is already in use') + process.exit(1) + break + default: + throw error + } +} + +/** + * Event listener for HTTP server "listening" event. + */ + +function onListening () { + var addr = server.address() + var bind = typeof addr === 'string' + ? 'pipe ' + addr + : 'port ' + addr.port + debug('Listening on ' + bind) +} diff --git a/mock_api/package.json b/mock_api/package.json new file mode 100644 index 0000000..e3df751 --- /dev/null +++ b/mock_api/package.json @@ -0,0 +1,17 @@ +{ + "name": "credit_offers_mock_api", + "version": "0.2.0", + "private": true, + "scripts": { + "start": "node ./bin/www" + }, + "dependencies": { + "body-parser": "~1.13.2", + "cookie-parser": "~1.3.5", + "debug": "~2.2.0", + "express": "~4.13.1", + "jade": "^1.11.0", + "morgan": "~1.6.1", + "request": "^2.69.0" + } +} diff --git a/mock_api/public/images/JB16760-Generic-VentureOne-EMV-flat-244x154-06-15.png b/mock_api/public/images/JB16760-Generic-VentureOne-EMV-flat-244x154-06-15.png new file mode 100644 index 0000000000000000000000000000000000000000..0e18f499a35f9cad888f9608b3dd1804b6fbfb34 GIT binary patch literal 23386 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 literal 0 HcmV?d00001 diff --git a/mock_api/public/images/www-venture-visa-sig-flat-9-14.png b/mock_api/public/images/www-venture-visa-sig-flat-9-14.png new file mode 100644 index 0000000000000000000000000000000000000000..d03e705c319bda101b196bd68bd08b02d37576c0 GIT binary patch literal 21550 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

{4sp3#$8Y0 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 Date: Tue, 18 Oct 2016 12:12:11 -0400 Subject: [PATCH 030/147] Update API version to 2 --- creditOffersClient.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/creditOffersClient.js b/creditOffersClient.js index cab9da4..1683d99 100644 --- a/creditOffersClient.js +++ b/creditOffersClient.js @@ -20,9 +20,8 @@ var debug = require('debug')('credit-offers:api-client') // Default to a secure call to the API endpoint var defaultOptions = { - // TODO: update this value with actual/staging api host url: 'https://api.capitalone.com', - apiVersion: 1 + apiVersion: 2 } /** From 3b5f97c22ce3e6df1b2ef7ee87d8bedb747d6eee Mon Sep 17 00:00:00 2001 From: rkorsak Date: Tue, 18 Oct 2016 12:23:53 -0400 Subject: [PATCH 031/147] Update default API version in config --- config.js.sample | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.js.sample b/config.js.sample index b9b32c9..9375d60 100644 --- a/config.js.sample +++ b/config.js.sample @@ -27,7 +27,7 @@ module.exports = { client: { // The URL of the Credit Offers environment you are connecting to. url: creditOffersHost, - apiVersion: 1 + apiVersion: 2 }, oauth: { tokenURL: oauthHost + '/oauth/oauth20/token', From 38aa8040f85350c3d36c64fca14bef753b04a796 Mon Sep 17 00:00:00 2001 From: rkorsak Date: Tue, 18 Oct 2016 14:31:36 -0400 Subject: [PATCH 032/147] Parse errors for all 4xx and 5xx responses --- creditOffersClient.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/creditOffersClient.js b/creditOffersClient.js index 1683d99..736fd82 100644 --- a/creditOffersClient.js +++ b/creditOffersClient.js @@ -72,7 +72,7 @@ CreditOffersClient.prototype.getTargetedProductsOffer = function getTargetedProd CreditOffersClient.prototype._sendRequest = function _sendRequest (reqOptions, callback) { request(reqOptions, function (err, response, body) { if (err) { return callback(err) } - if (response.statusCode === 400) { + if (response.statusCode >= 400) { return processResponseErrors(body, callback) } else if (response.statusCode === 200) { debug('Received response', body) From 986f40f48f7fd9df82ee9dba834cbc498b346a18 Mon Sep 17 00:00:00 2001 From: rkorsak Date: Tue, 18 Oct 2016 14:32:09 -0400 Subject: [PATCH 033/147] Moved response parsing out of the _sendRequest function --- creditOffersClient.js | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/creditOffersClient.js b/creditOffersClient.js index 736fd82..4a6856c 100644 --- a/creditOffersClient.js +++ b/creditOffersClient.js @@ -83,30 +83,30 @@ CreditOffersClient.prototype._sendRequest = function _sendRequest (reqOptions, c return callback(new Error(errorMessage)) } }) +} - function parseResponse (responseBody, callback) { - if (!responseBody) { - return callback(null, null) - } +function parseResponse (responseBody, callback) { + if (!responseBody) { + return callback(null, null) + } - try { - var responseObject = JSON.parse(responseBody) - return callback(null, responseObject) - } catch (error) { - return callback(error) - } + try { + var responseObject = JSON.parse(responseBody) + return callback(null, responseObject) + } catch (error) { + return callback(error) } +} - function processResponseErrors (responseBody, callback) { - parseResponse(responseBody, function (err, data) { - if (err) { return callback(err) } +function processResponseErrors (responseBody, callback) { + parseResponse(responseBody, function (err, data) { + if (err) { return callback(err) } - var errorCode = data.code || '' - var errorDescription = data.description || '' - var documentationUrl = data.documentationUrl || '' - var message = format('Received an error from the API: code=%s | description=%s | documentation=%s', errorCode, errorDescription, documentationUrl) - console.error(message) - callback(new Error(message)) - }) - } + var errorCode = data.code || '' + var errorDescription = data.description || '' + var documentationUrl = data.documentationUrl || '' + var message = format('Received an error from the API: code=%s | description=%s | documentation=%s', errorCode, errorDescription, documentationUrl) + console.error(message) + callback(new Error(message)) + }) } From 2c639a59da98d1b4ac4868cc502bbe9641bb248a Mon Sep 17 00:00:00 2001 From: rkorsak Date: Tue, 18 Oct 2016 14:32:38 -0400 Subject: [PATCH 034/147] Updated name, docs, and implementation of pre-qual --- creditOffersClient.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/creditOffersClient.js b/creditOffersClient.js index 4a6856c..f37c00e 100644 --- a/creditOffersClient.js +++ b/creditOffersClient.js @@ -41,19 +41,22 @@ function CreditOffersClient (options, oauth) { module.exports = CreditOffersClient /** - * Perform a request to retrieve credit offers for a customer + * Initiate a pre-qualification check for a customer. + * The response will include products for which the customer may be + * pre-qualified. If they do not qualify, at least one product will still + * be returned. * @param customerInfo {object} Represents the customer info to pass to the API */ -CreditOffersClient.prototype.getTargetedProductsOffer = function getTargetedProductsOffer (customerInfo, callback) { +CreditOffersClient.prototype.createPrequalificationCheck = function createPrequalificationCheck (customerInfo, callback) { var client = this this.oauth.withToken(function (err, token) { if (err) { return callback(err) } var reqOptions = { baseUrl: client.options.url, - url: '/credit-cards/targeted-product-offers', - method: 'GET', - qs: customerInfo, + url: '/credit-offers/prequalifications', + method: 'POST', + body: customerInfo, headers: { 'Accept': 'application/json; v=' + client.options.apiVersion }, @@ -61,7 +64,7 @@ CreditOffersClient.prototype.getTargetedProductsOffer = function getTargetedProd bearer: token.access_token } } - debug('Sending request for targeted product offers', reqOptions) + debug('Sending request to start pre-qualification', reqOptions) client._sendRequest(reqOptions, callback) }) } From 9940414827f2688c64b03970050f833b8972120b Mon Sep 17 00:00:00 2001 From: rkorsak Date: Tue, 18 Oct 2016 14:44:06 -0400 Subject: [PATCH 035/147] Adjusted client to always use JSON --- creditOffersClient.js | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/creditOffersClient.js b/creditOffersClient.js index f37c00e..f261d5b 100644 --- a/creditOffersClient.js +++ b/creditOffersClient.js @@ -56,6 +56,7 @@ CreditOffersClient.prototype.createPrequalificationCheck = function createPrequa baseUrl: client.options.url, url: '/credit-offers/prequalifications', method: 'POST', + json: true, body: customerInfo, headers: { 'Accept': 'application/json; v=' + client.options.apiVersion @@ -77,9 +78,9 @@ CreditOffersClient.prototype._sendRequest = function _sendRequest (reqOptions, c if (err) { return callback(err) } if (response.statusCode >= 400) { return processResponseErrors(body, callback) - } else if (response.statusCode === 200) { + } else if (response.statusCode >= 200) { debug('Received response', body) - parseResponse(body, callback) + return callback(null, body) } else { var errorMessage = 'Received unexpected status code: ' + response.statusCode console.error(errorMessage) @@ -88,19 +89,6 @@ CreditOffersClient.prototype._sendRequest = function _sendRequest (reqOptions, c }) } -function parseResponse (responseBody, callback) { - if (!responseBody) { - return callback(null, null) - } - - try { - var responseObject = JSON.parse(responseBody) - return callback(null, responseObject) - } catch (error) { - return callback(error) - } -} - function processResponseErrors (responseBody, callback) { parseResponse(responseBody, function (err, data) { if (err) { return callback(err) } From de1a4d0a0028d0e1cc9afd373729faf93f27e7ba Mon Sep 17 00:00:00 2001 From: rkorsak Date: Tue, 18 Oct 2016 15:35:11 -0400 Subject: [PATCH 036/147] Updated mock API with new prequal response --- mock_api/package.json | 2 + mock_api/routes/index.js | 133 +++++++++++++++++++-------------------- 2 files changed, 65 insertions(+), 70 deletions(-) diff --git a/mock_api/package.json b/mock_api/package.json index e3df751..499eec1 100644 --- a/mock_api/package.json +++ b/mock_api/package.json @@ -11,7 +11,9 @@ "debug": "~2.2.0", "express": "~4.13.1", "jade": "^1.11.0", + "lodash": "4.16.4", "morgan": "~1.6.1", + "node-uuid": "1.4.7", "request": "^2.69.0" } } diff --git a/mock_api/routes/index.js b/mock_api/routes/index.js index 8e96e2c..0ac3b82 100644 --- a/mock_api/routes/index.js +++ b/mock_api/routes/index.js @@ -1,141 +1,134 @@ var express = require('express') var router = express.Router() +var uuid = require('node-uuid') var fakeProducts = { 'good_credit': { - "applicationUrl": "https://applicationqa20.kdc.capitalone.com/icoreapp/jsp/landing.jsp?s=0013262006ER202e1e0f-2d99-4fd1-8796-c533657888d0Z&IARC=ASPEN&experience=ITA", + "productId": "1", + "applicationUrl": "https://www.capitalone.com?context=applicationUrl", "code": "VENTURE01", - "name": "Venture® Rewards", + "productDisplayName": "Venture® Rewards", "priority": 1, - "tier": "EXCELLENT CREDIT", + "tier": "ExcellentCredit", "images": [{ - "url": { - "href": "http://localhost:3002/images/www-venture-visa-sig-flat-9-14.png", - "method": "GET", - "type": "image/png" - }, - "height": 151, - "width": 240, - "alternateText": "Apply now for the Venture Rewards Card" - }, { - "url": { - "href": "http://localhost:3002/images/JB16760-GenericEMV-Venture-EMV-flat-244x154-06-15.png", - "method": "GET", - "type": "image/png" - }, "height": 154, "width": 244, - "alternateText": "Apply now for the Venture Rewards Card" + "alternateText": "Apply now for the Venture Rewards Card", + "url": "http://localhost:3002/images/JB16760-GenericEMV-Venture-EMV-flat-244x154-06-15.png", + "imageType": "CardArt" }], "terms": { "primaryBenefit": "Earn unlimited 2X miles per dollar on every purchase. Plus, earn 40,000 bonus miles once you spend $3,000 on purchases within the first 3 months.", "purchaseAprTerms": "13.24% to 23.24% variable APR", "balanceTransferTerms": "13.24% to 23.24% variable APR; No Transfer Fee", "annualMembershipFeeTerms": "$0 intro for the first year; $59 after that" - } + }, + "additionalInformationUrl": "https://www.capitalone.com?context=additionalInformationUrl" }, 'average_credit': { - "applicationUrl": "https://applicationqa20.kdc.capitalone.com/icoreapp/jsp/landing.jsp?s=0013262001ER202e1e0f-2d99-4fd1-8796-c533657888d0Z&IARC=ASPEN&experience=ITA", + "productId": "2", + "applicationUrl": "https://www.capitalone.com?context=applicationUrl", "code": "VENTUREONE01", - "name": "VentureOne® Rewards", + "productDisplayName": "VentureOne® Rewards", "priority": 2, - "tier": "EXCELLENT CREDIT", + "tier": "AverageCredit", "images": [{ - "url": { - "href": "http://localhost:3002/images/www-venture-visa-sig-flat-9-14.png", - "method": "GET", - "type": "image/png" - }, - "height": 151, - "width": 240, - "alternateText": "Apply now for the VentureOne Rewards Card" - }, { - "url": { - "href": "http://localhost:3002/images/JB16760-Generic-VentureOne-EMV-flat-244x154-06-15.png", - "method": "GET", - "type": "image/png" - }, "height": 154, "width": 244, - "alternateText": "Apply now for the VentureOne Rewards Card" + "alternateText": "Apply now for the VentureOne Rewards Card", + "url": "http://localhost:3002/images/JB16760-Generic-VentureOne-EMV-flat-244x154-06-15.png", + "imageType": "CardArt" }], "terms": { "primaryBenefit": "Earn unlimited 1.25 miles per dollar on every purchase. Plus, earn 20,000 bonus miles once you spend $1,000 on purchases within the first 3 months.\r\n", "purchaseAprTerms": "0% intro APR until February 2017; 12.24% to 22.24% variable APR after that", "balanceTransferTerms": "12.24% to 22.24% variable APR; No Transfer Fee", "annualMembershipFeeTerms": "$0" - } + }, + "additionalInformationUrl": "https://www.capitalone.com?context=additionalInformationUrl" }, 'bad_credit': { - "applicationUrl": "https://applicationqa20.kdc.capitalone.com/icoreapp/jsp/landing.jsp?s=0013264008ER202e1e0f-2d99-4fd1-8796-c533657888d0Z&IARC=ASPEN&experience=ITA", - "code": "PSECURED01", - "name": "Secured MasterCard®", + "productId": "3", + "applicationUrl": "https://www.capitalone.com?context=applicationUrl", + "code": "BLUESTEEL", + "name": "Blue Steel MasterCard®", "priority": 6, - "tier": "REBUILDING CREDIT", + "tier": "RebuildingCredit", "images": [{ - "url": { - "href": "http://localhost:3002/images/JB16760-MC-Blue-Steel-EMV-flat-244x154-06-15.png", - "method": "GET", - "type": "image/png" - }, "height": 154, "width": 244, - "alternateText": "Apply now for the Secured MasterCard Card" + "alternateText": "Apply now for the VentureOne Rewards Card", + "url": "http://localhost:3002/images/JB16760-MC-Blue-Steel-EMV-flat-244x154-06-15.png", + "imageType": "CardArt" }], - "additionalInformationUrl": "http://www.capitalone.com/credit-cards/LP/secured-mastercard-gen-faq", "terms": { "primaryBenefit": "Build your credit with responsible use. Refundable deposit of $49, $99 or $200 required.", "purchaseAprTerms": "24.99% variable APR", "balanceTransferTerms": "24.99% variable APR; No Transfer Fee", "annualMembershipFeeTerms": "$0" - } + }, + "additionalInformationUrl": "https://www.capitalone.com?context=additionalInformationUrl" } } -/* GET home page. */ -router.get('/credit-cards/targeted-product-offers', function (req, res, next) { - console.info(req.query) - var creditRating = req.query.selfAssessedCreditRating +/* POST to create a prequal check */ +router.post('/credit-cards/prequalifications', function (req, res, next) { + console.info(req.body) var response = {} var status = 200 - console.info('Determining response based on credit rating (%s)', creditRating) - switch (creditRating) { - case 'Excellent': + var lastName = req.body.lastName + + console.info('Determining response based on last name (%s)', lastName) + switch (lastName.toUpperCase().replace(' ', '')) { + case 'ONEPRODUCT': response = { isPrequalified: true, products: [ fakeProducts['good_credit'] ] } break - case 'Average': + case 'MULTIPROD': + case 'MULTIPLEPRODUCTS': response = { isPrequalified: true, - products: [ fakeProducts['average_credit'] ] + products: [ + fakeProducts['good_credit'], + fakeProducts['average_credit'], + fakeProducts['bad_credit'] + ] } break - case 'Rebuilding': + case 'NOTQUALIFIED': response = { isPrequalified: false, - products: [ fakeProducts['bad_credit'] ] + products: [ + fakeProducts['good_credit'], + fakeProducts['average_credit'], + fakeProducts['bad_credit'] + ] } break - case null: - case undefined: - case '': + case 'ERROR': + status = 400 response = { - isPrequalified: false, - products: [] + code: 202003, + description: 'Simulated error' } break default: - status = 400 response = { - code: 999, - description: 'Unknown credit rating: ' + creditRating + isPrequalified: false, + products: [ + fakeProducts['bad_credit'] + ] } break } + + var id = { 'prequalificationId': uuid.v4() } + var finalResponse = _.assign({}, req.body, id, response) + res.status(status) - res.json(response) + res.json(finalResponse) }) module.exports = router From 416a6ef3f20389b9a45ec23f2ecd7e1efda325f7 Mon Sep 17 00:00:00 2001 From: rkorsak Date: Tue, 18 Oct 2016 16:04:36 -0400 Subject: [PATCH 037/147] Updated client response parsing --- creditOffersClient.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/creditOffersClient.js b/creditOffersClient.js index f261d5b..bd4fc63 100644 --- a/creditOffersClient.js +++ b/creditOffersClient.js @@ -90,14 +90,14 @@ CreditOffersClient.prototype._sendRequest = function _sendRequest (reqOptions, c } function processResponseErrors (responseBody, callback) { - parseResponse(responseBody, function (err, data) { - if (err) { return callback(err) } + if (!responseBody) { + return callback(new Error('The request failed with no error details returned')) + } - var errorCode = data.code || '' - var errorDescription = data.description || '' - var documentationUrl = data.documentationUrl || '' - var message = format('Received an error from the API: code=%s | description=%s | documentation=%s', errorCode, errorDescription, documentationUrl) - console.error(message) - callback(new Error(message)) - }) + var errorCode = responseBody.code || '' + var errorDescription = responseBody.description || '' + var documentationUrl = responseBody.documentationUrl || '' + var message = format('Received an error from the API: code=%s | description=%s | documentation=%s', errorCode, errorDescription, documentationUrl) + console.error(message) + callback(new Error(message)) } From f30f302d9c94210d7af9c455dcba83219457c884 Mon Sep 17 00:00:00 2001 From: rkorsak Date: Tue, 18 Oct 2016 16:05:10 -0400 Subject: [PATCH 038/147] Fix mock API prequal route --- mock_api/routes/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mock_api/routes/index.js b/mock_api/routes/index.js index 0ac3b82..fb73949 100644 --- a/mock_api/routes/index.js +++ b/mock_api/routes/index.js @@ -1,6 +1,7 @@ var express = require('express') var router = express.Router() var uuid = require('node-uuid') +var _ = require('lodash') var fakeProducts = { 'good_credit': { @@ -72,7 +73,7 @@ var fakeProducts = { } /* POST to create a prequal check */ -router.post('/credit-cards/prequalifications', function (req, res, next) { +router.post('/credit-offers/prequalifications', function (req, res, next) { console.info(req.body) var response = {} var status = 200 From f879016b819c46a9fad38e243f59edf0ee6fb826 Mon Sep 17 00:00:00 2001 From: rkorsak Date: Tue, 18 Oct 2016 16:06:11 -0400 Subject: [PATCH 039/147] Update route and views for prequal --- routes/offers.js | 21 +++++++++++++++++++-- views/includes/customer-form.jade | 5 +++++ views/offers.jade | 4 ++-- 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/routes/offers.js b/routes/offers.js index 44dbad0..c2dbc2f 100644 --- a/routes/offers.js +++ b/routes/offers.js @@ -28,13 +28,30 @@ module.exports = function (options) { // POST customer info to check for offers router.post('/', csrfProtection, function (req, res, next) { - var customerInfo = req.body - client.getTargetedProductsOffer(customerInfo, function (err, response) { + // Build the customer info (moving address into its own object) + // NOTE: In a production app, make sure to perform more in-depth validation of your inputs + var customerInfo = _.assign({}, req.body) + var addressProps = [ + 'addressLine1', + 'addressLine2', + 'addressLine3', + 'addressLine4', + 'city', + 'stateCode', + 'postalCode', + 'addressType' + ] + var address = _.pick(customerInfo, addressProps) + customerInfo = _.omit(customerInfo, addressProps) + customerInfo.address = address + + client.createPrequalificationCheck(customerInfo, function (err, response) { if (err) { return next(err) } var viewModel = { title: 'Credit Offers', isPrequalified: response.isPrequalified, + prequalificationId: response.prequalificationId, products: response.products && _.sortBy(response.products, 'priority') } diff --git a/views/includes/customer-form.jade b/views/includes/customer-form.jade index ad372d5..ca9fc72 100644 --- a/views/includes/customer-form.jade +++ b/views/includes/customer-form.jade @@ -94,10 +94,15 @@ mixin stateOptions() +textinput('nameSuffix', 'Suffix')(placeholder='E.g. Jr, Sr') +textinput('addressLine1', 'Address Line 1', true) +textinput('addressLine2', 'Address Line 2') ++textinput('addressLine3', 'Address Line 3') ++textinput('addressLine4', 'Address Line 4') +textinput('city', 'City', true) +select('stateCode', 'State', true) +stateOptions() +textinput('postalCode', 'Zip Code', true) ++select('addressType', 'Address Type', true) + option(value='Home') Home + option(value='Business') Business +textinput('taxId', 'Last Four Digits of SSN', true) +textinput('dateOfBirth', 'Birth Date', false)(placeholder='YYYY-MM-DD') diff --git a/views/offers.jade b/views/offers.jade index f371d96..8e864e6 100644 --- a/views/offers.jade +++ b/views/offers.jade @@ -30,10 +30,10 @@ block content div.product-offer.media div.media-left img.product-image( - src = product.images[0].url.href + src = product.images[0].url alt = product.images[0].alternateText ) div.media-body - h4.media-heading= product.name + h4.media-heading= product.productDisplayName if product.terms && product.terms.primaryBenefit div.primary-benefit= product.terms.primaryBenefit From e2a7a8b1726f596cdbbb743689b354b0833f9d84 Mon Sep 17 00:00:00 2001 From: rkorsak Date: Tue, 18 Oct 2016 16:06:32 -0400 Subject: [PATCH 040/147] Fix display name in mock data --- mock_api/routes/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mock_api/routes/index.js b/mock_api/routes/index.js index fb73949..4fa68ee 100644 --- a/mock_api/routes/index.js +++ b/mock_api/routes/index.js @@ -52,7 +52,7 @@ var fakeProducts = { "productId": "3", "applicationUrl": "https://www.capitalone.com?context=applicationUrl", "code": "BLUESTEEL", - "name": "Blue Steel MasterCard®", + "productDisplayName": "Blue Steel MasterCard®", "priority": 6, "tier": "RebuildingCredit", "images": [{ From 584b7d828935cf1477b89c9b64317e22f534c14c Mon Sep 17 00:00:00 2001 From: rkorsak Date: Tue, 18 Oct 2016 15:12:32 -0400 Subject: [PATCH 041/147] Add prequal ack to client --- creditOffersClient.js | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/creditOffersClient.js b/creditOffersClient.js index bd4fc63..20dca1f 100644 --- a/creditOffersClient.js +++ b/creditOffersClient.js @@ -70,6 +70,34 @@ CreditOffersClient.prototype.createPrequalificationCheck = function createPrequa }) } +/** + * Acknowledges that a set of pre-qualification results have been displayed + * to the consumer. + * @param prequalificationId {string} The unique identifier of the prequalification request to acknowledge + */ +CreditOffersClient.prototype.acknowledgePrequalification = function acknowledgePrequalification (prequalificationId, callback) { + var client = this + this.oauth.withToken(function (err, token) { + if (err) { return callback(err) } + + var reqOptions = { + baseUrl: client.options.url, + url: '/credit-offers/prequalifications/' + prequalificationId, + method: 'POST', + json: true, + body: { 'hasBeenAcknowledged': true }, + headers: { + 'Accept': 'application/json; v=' + client.options.apiVersion + }, + auth: { + bearer: token.access_token + } + } + debug('Sending request to acknowledge pre-qualification ID prequalificationId', reqOptions) + client._sendRequest(reqOptions, callback) + }) +} + /** * A private function to send a request to the API and parse the response, handling errors as needed */ From 6fd4f982b6950ff2cb4a93ea5194bf720067f46e Mon Sep 17 00:00:00 2001 From: rkorsak Date: Wed, 19 Oct 2016 12:41:00 -0400 Subject: [PATCH 042/147] Prequal ack in mock API and UI --- creditOffersClient.js | 4 +++- mock_api/app.js | 4 ++-- mock_api/routes/{index.js => prequal.js} | 19 +++++++++++++++++++ routes/offers.js | 17 +++++++++++++++++ views/index.jade | 2 +- views/layout.jade | 1 + views/offers.jade | 21 ++++++++++++++++++++- 7 files changed, 63 insertions(+), 5 deletions(-) rename mock_api/routes/{index.js => prequal.js} (89%) diff --git a/creditOffersClient.js b/creditOffersClient.js index 20dca1f..5297cc9 100644 --- a/creditOffersClient.js +++ b/creditOffersClient.js @@ -77,12 +77,14 @@ CreditOffersClient.prototype.createPrequalificationCheck = function createPrequa */ CreditOffersClient.prototype.acknowledgePrequalification = function acknowledgePrequalification (prequalificationId, callback) { var client = this + if (!prequalificationId) { return callback(new Error('Unable to acknowledge prequalification without an ID')) } + this.oauth.withToken(function (err, token) { if (err) { return callback(err) } var reqOptions = { baseUrl: client.options.url, - url: '/credit-offers/prequalifications/' + prequalificationId, + url: '/credit-offers/prequalifications/' + encodeURIComponent(prequalificationId), method: 'POST', json: true, body: { 'hasBeenAcknowledged': true }, diff --git a/mock_api/app.js b/mock_api/app.js index b8a6210..a44deee 100644 --- a/mock_api/app.js +++ b/mock_api/app.js @@ -4,7 +4,7 @@ var logger = require('morgan') var cookieParser = require('cookie-parser') var bodyParser = require('body-parser') -var routes = require('./routes/index') +var prequal = require('./routes/prequal') var oauth = require('./routes/oauth') var app = express() @@ -19,7 +19,7 @@ app.use(bodyParser.urlencoded({ extended: false })) app.use(cookieParser()) app.use(express.static(path.join(__dirname, 'public'))) -app.use('/', routes) +app.use('/', prequal) app.use('/oauth/', oauth) // catch 404 and forward to error handler diff --git a/mock_api/routes/index.js b/mock_api/routes/prequal.js similarity index 89% rename from mock_api/routes/index.js rename to mock_api/routes/prequal.js index 4fa68ee..e7d28c0 100644 --- a/mock_api/routes/index.js +++ b/mock_api/routes/prequal.js @@ -132,4 +132,23 @@ router.post('/credit-offers/prequalifications', function (req, res, next) { res.json(finalResponse) }) +/* POST to acknowledge the results of a prequal check */ +router.post('/credit-offers/prequalifications/:id', function (req, res, next) { + console.info('Acknowledging prequal ID ' + req.params.id) + console.info(req.body) + var response = null + var status = 204 + + if (!req.body.hasBeenAcknowledged) { + status = 400 + response = { + code: 202028, + description: 'The value of the hasBeenAcknowledged body property is invalid. Must be set to true.' + } + } + + res.status(status) + res.json(response) +}) + module.exports = router diff --git a/routes/offers.js b/routes/offers.js index c2dbc2f..772dc51 100644 --- a/routes/offers.js +++ b/routes/offers.js @@ -59,5 +59,22 @@ module.exports = function (options) { }) }) + // POST acknowledgement that prequal offers were displayed + router.post('/acknowledge/:id', function (req, res, next) { + var id = req.params.id + if (!id) { + res.status(400).send() + return + } + + client.acknowledgePrequalification(id, function (err, response) { + if (err) { + res.status(500).send() + return + } + res.status(200).send() + }) + }) + return router } diff --git a/views/index.jade b/views/index.jade index c3adfc9..e0f3c75 100644 --- a/views/index.jade +++ b/views/index.jade @@ -11,7 +11,7 @@ //- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. //- See the License for the specific language governing permissions and limitations under the License. -extends ./layout.jade +extends ./layout block content div.jumbotron diff --git a/views/layout.jade b/views/layout.jade index 42c1d0e..f06aeda 100644 --- a/views/layout.jade +++ b/views/layout.jade @@ -27,3 +27,4 @@ head div.container block content block modals + block scripts diff --git a/views/offers.jade b/views/offers.jade index 8e864e6..13c5d6f 100644 --- a/views/offers.jade +++ b/views/offers.jade @@ -11,7 +11,7 @@ //- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. //- See the License for the specific language governing permissions and limitations under the License. -extends ./layout.jade +extends ./layout block content p.offer-summary @@ -37,3 +37,22 @@ block content h4.media-heading= product.productDisplayName if product.terms && product.terms.primaryBenefit div.primary-benefit= product.terms.primaryBenefit + +block scripts + script. + $(function() { + var prequalificationId = '#{prequalificationId}' + if (!prequalificationId) { + console.warn('Displayed offers without a prequalification ID') + return; + } + + // Tell the server that we have displayed the products to the user + $.post('/offers/acknowledge/' + encodeURIComponent(prequalificationId)) + .done(function () { + console.log('Successfully acknowledged prequalification products') + }) + .fail(function (res, st, err) { + console.warn('Failed to acknowledge prequalification products: ' + err) + }) + }) From 678f91fefd4d73c3192ac16adc6c1caf9106311b Mon Sep 17 00:00:00 2001 From: rkorsak Date: Fri, 21 Oct 2016 12:27:34 -0400 Subject: [PATCH 043/147] Refactored API client into multiple files --- creditOffersClient.js | 133 ------------------------------- creditoffers/client.js | 108 +++++++++++++++++++++++++ creditoffers/index.js | 33 ++++++++ creditoffers/prequalification.js | 62 ++++++++++++++ routes/offers.js | 11 ++- 5 files changed, 210 insertions(+), 137 deletions(-) delete mode 100644 creditOffersClient.js create mode 100644 creditoffers/client.js create mode 100644 creditoffers/index.js create mode 100644 creditoffers/prequalification.js diff --git a/creditOffersClient.js b/creditOffersClient.js deleted file mode 100644 index 5297cc9..0000000 --- a/creditOffersClient.js +++ /dev/null @@ -1,133 +0,0 @@ -/* -Copyright 2016 Capital One Services, LLC - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and limitations under the License. -*/ - -var request = require('request') -var _ = require('lodash') -var format = require('util').format -var debug = require('debug')('credit-offers:api-client') - -// Default to a secure call to the API endpoint -var defaultOptions = { - url: 'https://api.capitalone.com', - apiVersion: 2 -} - -/** - * The API client class - * @param options {object} Client options (host url, API version) - */ -function CreditOffersClient (options, oauth) { - if (!this instanceof CreditOffersClient) { - return new CreditOffersClient(options) - } - - // Store the supplied options, using default values if not specified - this.options = _.defaults({}, options, defaultOptions) - this.oauth = oauth - debug('Initializing Credit Offers client', this.options) -} -module.exports = CreditOffersClient - -/** - * Initiate a pre-qualification check for a customer. - * The response will include products for which the customer may be - * pre-qualified. If they do not qualify, at least one product will still - * be returned. - * @param customerInfo {object} Represents the customer info to pass to the API - */ -CreditOffersClient.prototype.createPrequalificationCheck = function createPrequalificationCheck (customerInfo, callback) { - var client = this - this.oauth.withToken(function (err, token) { - if (err) { return callback(err) } - - var reqOptions = { - baseUrl: client.options.url, - url: '/credit-offers/prequalifications', - method: 'POST', - json: true, - body: customerInfo, - headers: { - 'Accept': 'application/json; v=' + client.options.apiVersion - }, - auth: { - bearer: token.access_token - } - } - debug('Sending request to start pre-qualification', reqOptions) - client._sendRequest(reqOptions, callback) - }) -} - -/** - * Acknowledges that a set of pre-qualification results have been displayed - * to the consumer. - * @param prequalificationId {string} The unique identifier of the prequalification request to acknowledge - */ -CreditOffersClient.prototype.acknowledgePrequalification = function acknowledgePrequalification (prequalificationId, callback) { - var client = this - if (!prequalificationId) { return callback(new Error('Unable to acknowledge prequalification without an ID')) } - - this.oauth.withToken(function (err, token) { - if (err) { return callback(err) } - - var reqOptions = { - baseUrl: client.options.url, - url: '/credit-offers/prequalifications/' + encodeURIComponent(prequalificationId), - method: 'POST', - json: true, - body: { 'hasBeenAcknowledged': true }, - headers: { - 'Accept': 'application/json; v=' + client.options.apiVersion - }, - auth: { - bearer: token.access_token - } - } - debug('Sending request to acknowledge pre-qualification ID prequalificationId', reqOptions) - client._sendRequest(reqOptions, callback) - }) -} - -/** - * A private function to send a request to the API and parse the response, handling errors as needed - */ -CreditOffersClient.prototype._sendRequest = function _sendRequest (reqOptions, callback) { - request(reqOptions, function (err, response, body) { - if (err) { return callback(err) } - if (response.statusCode >= 400) { - return processResponseErrors(body, callback) - } else if (response.statusCode >= 200) { - debug('Received response', body) - return callback(null, body) - } else { - var errorMessage = 'Received unexpected status code: ' + response.statusCode - console.error(errorMessage) - return callback(new Error(errorMessage)) - } - }) -} - -function processResponseErrors (responseBody, callback) { - if (!responseBody) { - return callback(new Error('The request failed with no error details returned')) - } - - var errorCode = responseBody.code || '' - var errorDescription = responseBody.description || '' - var documentationUrl = responseBody.documentationUrl || '' - var message = format('Received an error from the API: code=%s | description=%s | documentation=%s', errorCode, errorDescription, documentationUrl) - console.error(message) - callback(new Error(message)) -} diff --git a/creditoffers/client.js b/creditoffers/client.js new file mode 100644 index 0000000..f4fe1f1 --- /dev/null +++ b/creditoffers/client.js @@ -0,0 +1,108 @@ +/* +Copyright 2016 Capital One Services, LLC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and limitations under the License. +*/ + +var request = require('request') +var _ = require('lodash') +var format = require('util').format +var debug = require('debug')('credit-offers:api-client') + +// Default to a secure call to the API endpoint +var defaultOptions = { + url: 'https://api.capitalone.com', + apiVersion: 2 +} + +/** + * The API client class + * @param options {object} Client options (host url, API version) + */ +function ApiClient (options, oauth) { + if (!this instanceof ApiClient) { + return new ApiClient(options) + } + + // Store the supplied options, using default values if not specified + this.options = _.defaults({}, options, defaultOptions) + this.oauth = oauth + debug('Initializing API client', this.options) +} +module.exports = ApiClient + +/** + * Send a request to the API and parse the response, handling oauth and errors as needed + */ +ApiClient.prototype.sendRequest = function _sendRequest (reqOptions, callback) { + var defaultRequestSettings = { + baseUrl: this.options.url, + json: true, + headers: { + 'Accept': 'application/json; v=' + this.options.apiVersion + } + } + + // Populate the above request defaults if not passed in + _.defaults(reqOptions, defaultRequestSettings) + + debug('Sending request', reqOptions) + + if (reqOptions.useOAuth) + { + // Wrap the request in a call to get the oauth token + this.oauth.withToken(function (err, token) { + if (err) { return callback(err) } + reqOptions.auth = { bearer: token.access_token } + request(reqOptions, function (err, response, body) { + processResponse(err, response, body, callback) + }) + }) + } + else { + request(reqOptions, function (err, response, body) { + processResponse(err, response, body, callback) + }) + } +} + +function processResponse (err, response, body, callback) { + if (err) { return callback(err) } + + if (response.statusCode >= 400) { + // If the status code is an error, look for more info in the response body + debug('Received error status code ' + response.statusCode) + return processResponseErrors(body, callback) + } else if (response.statusCode >= 200) { + // Pass the body contents back to the caller + debug('Received response', body) + return callback(null, body) + } else { + // Unknown status code + var errorMessage = 'Received unexpected status code: ' + response.statusCode + console.error(errorMessage) + return callback(new Error(errorMessage)) + } +} + +function processResponseErrors (responseBody, callback) { + if (!responseBody) { + return callback(new Error('The request failed with no error details returned')) + } + + var errorCode = responseBody.code || '' + var errorDescription = responseBody.description || '' + var documentationUrl = responseBody.documentationUrl || '' + var message = format('Received an error from the API: code=%s | description=%s | documentation=%s', errorCode, errorDescription, documentationUrl) + console.error(message) + callback(new Error(message)) +} diff --git a/creditoffers/index.js b/creditoffers/index.js new file mode 100644 index 0000000..04e59b5 --- /dev/null +++ b/creditoffers/index.js @@ -0,0 +1,33 @@ +/* +Copyright 2016 Capital One Services, LLC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and limitations under the License. +*/ + +var ApiClient = require('./client') +var Prequalification = require('./prequalification') + +/** + * The Credit Offers API client + * @param options {object} Client options (host url, API version) + * @param oauth {object} The OAuth handler + */ +function CreditOffers (options, oauth) { + if (!this instanceof CreditOffers) { + return new CreditOffers(options, oauth) + } + + var client = new ApiClient(options, oauth) + this.prequalification = new Prequalification(client) +} + +module.exports = CreditOffers diff --git a/creditoffers/prequalification.js b/creditoffers/prequalification.js new file mode 100644 index 0000000..3e36115 --- /dev/null +++ b/creditoffers/prequalification.js @@ -0,0 +1,62 @@ +/* +Copyright 2016 Capital One Services, LLC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and limitations under the License. +*/ + +var request = require('request') +var debug = require('debug')('credit-offers:api-client') + +/** + * Contains all functions for interacting with the credit offer prequalification API + * @param {object} client The API client + */ +function Prequalification (client) { + if (!this instanceof Prequalification) { + return new Prequalification(client) + } + + this.client = client +} +module.exports = Prequalification + +/** + * Initiate a pre-qualification check for a customer. + * The response will include products for which the customer may be + * pre-qualified. If they do not qualify, at least one product will still + * be returned. + * @param customerInfo {object} Represents the customer info to pass to the API + */ +Prequalification.prototype.create = function create (customerInfo, callback) { + this.client.sendRequest({ + url: '/credit-offers/prequalifications', + useOAuth: true, + method: 'POST', + body: customerInfo + }, callback) +} + +/** + * Acknowledges that a set of pre-qualification results have been displayed + * to the consumer. + * @param prequalificationId {string} The unique identifier of the prequalification request to acknowledge + */ +Prequalification.prototype.acknowledge = function acknowledge (prequalificationId, callback) { + if (!prequalificationId) { return callback(new Error('Unable to acknowledge prequalification without an ID')) } + + this.client.sendRequest({ + url: '/credit-offers/prequalifications/' + encodeURIComponent(prequalificationId), + useOAuth: true, + method: 'POST', + body: { 'hasBeenAcknowledged': true } + }, callback) +} diff --git a/routes/offers.js b/routes/offers.js index 772dc51..3e43f2d 100644 --- a/routes/offers.js +++ b/routes/offers.js @@ -18,12 +18,13 @@ See the License for the specific language governing permissions and limitations var express = require('express') var _ = require('lodash') var csrf = require('csurf') -var CreditOffersClient = require('../creditOffersClient') +var CreditOffers = require('../creditoffers') var oauth = require('../oauth') +var debug = require('debug')('credit-offers:offers') module.exports = function (options) { var router = express.Router() - var client = new CreditOffersClient(options.client, oauth(options.oauth)) + var client = new CreditOffers(options.client, oauth(options.oauth)) var csrfProtection = csrf({ cookie: true }) // POST customer info to check for offers @@ -45,7 +46,7 @@ module.exports = function (options) { customerInfo = _.omit(customerInfo, addressProps) customerInfo.address = address - client.createPrequalificationCheck(customerInfo, function (err, response) { + client.prequalification.create(customerInfo, function (err, response) { if (err) { return next(err) } var viewModel = { @@ -61,14 +62,16 @@ module.exports = function (options) { // POST acknowledgement that prequal offers were displayed router.post('/acknowledge/:id', function (req, res, next) { + debug('Received acknowledgement of ' + req.params.id) var id = req.params.id if (!id) { res.status(400).send() return } - client.acknowledgePrequalification(id, function (err, response) { + client.prequalification.acknowledge(id, function (err, response) { if (err) { + debug('Error in API call', err) res.status(500).send() return } From 6e6c0d66b46a72f4fd5e654e83adfd053857f793 Mon Sep 17 00:00:00 2001 From: rkorsak Date: Fri, 21 Oct 2016 12:54:23 -0400 Subject: [PATCH 044/147] Add product listings API client --- creditoffers/index.js | 3 ++ creditoffers/products.js | 76 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+) create mode 100644 creditoffers/products.js diff --git a/creditoffers/index.js b/creditoffers/index.js index 04e59b5..ccd6ae2 100644 --- a/creditoffers/index.js +++ b/creditoffers/index.js @@ -15,6 +15,7 @@ See the License for the specific language governing permissions and limitations var ApiClient = require('./client') var Prequalification = require('./prequalification') +var Products = require('./products') /** * The Credit Offers API client @@ -27,7 +28,9 @@ function CreditOffers (options, oauth) { } var client = new ApiClient(options, oauth) + this.prequalification = new Prequalification(client) + this.products = new Products(client) } module.exports = CreditOffers diff --git a/creditoffers/products.js b/creditoffers/products.js new file mode 100644 index 0000000..a00e89f --- /dev/null +++ b/creditoffers/products.js @@ -0,0 +1,76 @@ +/* +Copyright 2016 Capital One Services, LLC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and limitations under the License. +*/ + +var request = require('request') +var debug = require('debug')('credit-offers:api-client') + +/** + * Contains all functions for interacting with the credit offer product listings API + * @param {object} client The API client + */ +function Products (client) { + if (!this instanceof Products) { + return new Products(client) + } + + this.client = client +} +module.exports = Products + +/** + * Retrieve summary information on all products + * @param {object} pagingOptions Optionally control the number of results and starting offset + * in the result set + */ +Products.prototype.getAll = function getAll (pagingOptions, callback) { + var query = _.pick(pagingOptions, ['limit', 'offset']) + + this.client.sendRequest({ + url: '/credit-offers/products', + useOAuth: true, + method: 'GET', + query: query + }, callback) +} + +/** + * Retrieve summary information on all consumer card products + * @param {object} pagingOptions Optionally control the number of results and starting offset + * in the result set + */ +Products.prototype.getConsumerCards = function getConsumerCards (pagingOptions, callback) { + var query = _.pick(pagingOptions, ['limit', 'offset']) + + this.client.sendRequest({ + url: '/credit-offers/products/consumer-cards', + useOAuth: true, + method: 'GET', + query: query + }, callback) +} + +/** + * Retrieve summary information on all products + * @param {string} productId The ID of the consumer card product for which to retrieve details + */ +Products.prototype.getConsumerCardDetail = function getConsumerCardDetail (productId, callback) { + if (!productId) { callback(new Error('A product ID must be specified in order to retrieve product details')) } + + this.client.sendRequest({ + url: '/credit-offers/products/consumer-cards/' + encodeURIComponent(productId), + useOAuth: true, + method: 'GET' + }, callback) +} From 59a50a25bc915dda626b8d11b2c0ab420df4f89c Mon Sep 17 00:00:00 2001 From: rkorsak Date: Tue, 25 Oct 2016 20:34:07 -0400 Subject: [PATCH 045/147] Update Mock API with product routes --- mock_api/app.js | 2 + mock_api/mockproducts.js | 123 ++++++++++++++++++++++++++++++++++++ mock_api/package.json | 1 + mock_api/routes/prequal.js | 6 +- mock_api/routes/products.js | 83 ++++++++++++++++++++++++ 5 files changed, 212 insertions(+), 3 deletions(-) create mode 100644 mock_api/mockproducts.js create mode 100644 mock_api/routes/products.js diff --git a/mock_api/app.js b/mock_api/app.js index a44deee..f4dfc0c 100644 --- a/mock_api/app.js +++ b/mock_api/app.js @@ -6,6 +6,7 @@ var bodyParser = require('body-parser') var prequal = require('./routes/prequal') var oauth = require('./routes/oauth') +var products = require('./routes/products') var app = express() @@ -20,6 +21,7 @@ app.use(cookieParser()) app.use(express.static(path.join(__dirname, 'public'))) app.use('/', prequal) +app.use('/', products) app.use('/oauth/', oauth) // catch 404 and forward to error handler diff --git a/mock_api/mockproducts.js b/mock_api/mockproducts.js new file mode 100644 index 0000000..4deb572 --- /dev/null +++ b/mock_api/mockproducts.js @@ -0,0 +1,123 @@ +var _ = require('lodash') +var format = require('util').format +var moment = require('moment') +var debug = require('debug')('credit-offers_mock_api:mockproducts') + +function numberFormat (formatString, max, sampleSize) { + return function () { + var nums = _.sampleSize(_.range(max), _.random(sampleSize)) + return _.map(nums, function (num) { return format(formatString, num)}) + } +} + +// Simple constant values to populate in each product +var constants = { + "creditRating": [], + "balanceTransferFeeDescription": "", + "introBalanceTransferAPRDescription": "", + "balanceTransferAPRDescription": "", + "introPurchaseAPRDescription": "", + "purchaseAPRDescription": "", + "annualMembershipFeeDescription": "", + "rewardsRateDescription": "", + "foreignTransactionFeeDescription": "", + "fraudCoverageDescription": "", + "latePaymentDescription": "", + "penaltyAPRDescription": "", + "cashAdvanceFeeDescription": "", + "cashAdvanceAPRDescription": "", + "generalDescription": "", + "promotionalDescriptions": [] +} + +// Dynamic product properties to be built per product +var customProps = [ + // Set up randomized props + { + activeFrom: function () { + return moment().startOf('day').subtract(_.random(5), 'years').format() + }, + activeTo: function() { + return moment().startOf('day').add(_.random(1,5), 'years').format() + }, + applyNowLink: function (product) { + return format('https://capitalone.com?fake=applyNowLink&productId=%s', product.productId) + }, + brandName: _.partial(_.sample, ['Cool Brand', 'Great Brand', 'Awesome Brand']), + categoryTags: numberFormat('Tag %s', 100, 5), + productKeywords: numberFormat('Keyword %s', 100, 5), + marketingCopy: function () { return [] }, + processingNetwork: _.partial(_.sample, ['Visa', 'MasterCard']), + rewardsType: _.partial(_.sample, [null, 'Miles', 'Points']), + primaryBenefitDescription: function () { + return format('Earn unlimited %d miles per dollar on every purchase. Plus, earn %d0,000 bonus miles once you spend $%d,000 on purchases within the first %d months.', _.random(1,3), _.random(2,5), _.random(1,3), _.random(1,5)) + } + }, + // Set up props that rely on the previous pass + { + productDisplayName: function (product) { + return format('Fake %s %s %s', product.productType, product.processingNetwork, product.productId) + }, + publishedDate: function (product) { + return moment(product.activeFrom).subtract(_.random(1, 20), 'days').format() + } + }, + // Finally, set up the images (which require the display name from the previous step) + { + images: function (product) { + var filename = _.sample([ + 'JB16760-GenericEMV-Venture-EMV-flat-244x154-06-15.png', + 'JB16760-Generic-VentureOne-EMV-flat-244x154-06-15.png', + 'JB16760-MC-Blue-Steel-EMV-flat-244x154-06-15.png', + 'www-venture-visa-sig-flat-9-14.png' + ]) + return [{ + height: 154, + width: 244, + alternateText: format('Apply now for the %s Card', product.productDisplayName), + url: 'http://localhost:3002/images/' + filename, + imageType: 'CardName' + }] + } + } +] + +// Apply a single set of dynamic properties to a product +function applyProps (product, propSpecs) { + var newProps = _.map(propSpecs, function (propFn, propName) { + return [propName, propFn(product)] + }) + return _.defaults(product, _.fromPairs(newProps)) +} + +// Add all dynamic properties to a product +function addProps (product) { + var withConstants = _.defaults(product, constants) + return _.reduce(customProps, applyProps, withConstants) +} + +// Generate a collection of fake products based on a spec +// Spec is an object keyed by product type, containing the number of products for that type +function generate (spec) { + var totalCount = _.sum(_.values(spec)) + var ids = _.range(1, totalCount + 1) + var productTypes = _(spec) + .flatMap(function (count, productType) { + return _().range(count).map(() => productType).value() + }) + .shuffle() + + var startingProducts = productTypes.zipWith(ids, function (productType, id) { + return { + productId: id.toString(), + productType: productType + } + }) + + return startingProducts + .map(addProps) + .sortBy(function (product) { return parseInt(product.productId) }) + .value() +} + +module.exports = generate diff --git a/mock_api/package.json b/mock_api/package.json index 499eec1..01d4c7f 100644 --- a/mock_api/package.json +++ b/mock_api/package.json @@ -12,6 +12,7 @@ "express": "~4.13.1", "jade": "^1.11.0", "lodash": "4.16.4", + "moment": "2.15.2", "morgan": "~1.6.1", "node-uuid": "1.4.7", "request": "^2.69.0" diff --git a/mock_api/routes/prequal.js b/mock_api/routes/prequal.js index e7d28c0..5112983 100644 --- a/mock_api/routes/prequal.js +++ b/mock_api/routes/prequal.js @@ -16,7 +16,7 @@ var fakeProducts = { "width": 244, "alternateText": "Apply now for the Venture Rewards Card", "url": "http://localhost:3002/images/JB16760-GenericEMV-Venture-EMV-flat-244x154-06-15.png", - "imageType": "CardArt" + "imageType": "CardName" }], "terms": { "primaryBenefit": "Earn unlimited 2X miles per dollar on every purchase. Plus, earn 40,000 bonus miles once you spend $3,000 on purchases within the first 3 months.", @@ -38,7 +38,7 @@ var fakeProducts = { "width": 244, "alternateText": "Apply now for the VentureOne Rewards Card", "url": "http://localhost:3002/images/JB16760-Generic-VentureOne-EMV-flat-244x154-06-15.png", - "imageType": "CardArt" + "imageType": "CardName" }], "terms": { "primaryBenefit": "Earn unlimited 1.25 miles per dollar on every purchase. Plus, earn 20,000 bonus miles once you spend $1,000 on purchases within the first 3 months.\r\n", @@ -60,7 +60,7 @@ var fakeProducts = { "width": 244, "alternateText": "Apply now for the VentureOne Rewards Card", "url": "http://localhost:3002/images/JB16760-MC-Blue-Steel-EMV-flat-244x154-06-15.png", - "imageType": "CardArt" + "imageType": "CardName" }], "terms": { "primaryBenefit": "Build your credit with responsible use. Refundable deposit of $49, $99 or $200 required.", diff --git a/mock_api/routes/products.js b/mock_api/routes/products.js new file mode 100644 index 0000000..2891f19 --- /dev/null +++ b/mock_api/routes/products.js @@ -0,0 +1,83 @@ +var express = require('express') +var router = express.Router() +var _ = require('lodash') +var mockproducts = require('../mockproducts') +var debug = require('debug')('credit-offers_mock_api:products') +var format = require('util').format + +var fakeProducts = mockproducts({ + ConsumerCard: 100, + BusinessCard: 100 +}) + +function makeProductHeader (product) { + return _.pick(product, [ + 'productId', + 'productDisplayName', + 'activeFrom', + 'activeTo', + 'publishedDate', + 'applyNowLink', + 'productType' + ]) +} + +// Pagination middleware +function paginate (req, res, next) { + var products = res.locals.products + if (!products) { return next() } + + var limit = req.query.limit || 50 + var offset = req.query.offset || 0 + + res.locals.products = products.drop(offset).take(limit) + + next() +} + +// Middleware to unpack products and format a response +function makeResponse (req, res, next) { + var products = res.locals.products || _([]) + var response = { products: products.value() } + res.json(response) +} + +/* GET all cards */ +router.get('/credit-offers/products/cards', function (req, res, next) { + console.info(req.body) + res.locals.products = _(fakeProducts).map(makeProductHeader) + next() +}, +paginate, +makeResponse) + +/* GET cards for a specific type */ +router.get('/credit-offers/products/cards/:cardType', function (req, res, next) { + console.info(req.body) + + var cardType = req.params.cardType + res.locals.products = _(fakeProducts).filter({ productType: cardType }) + next() +}, +paginate, +makeResponse) + +/* GET details for a specific product */ +router.get('/credit-offers/products/cards/:cardType/:productId', function (req, res, next) { + console.info(req.body) + + var cardType = req.params.cardType + var productId = req.params.productId + debug(format('Looking for a card of type %s with ID %s', cardType, productId)) + var product = _(fakeProducts).filter({ productType: cardType, productId: productId }).head() + + if (product) + { + res.json(product) + } else { + res.status(404) + res.end() + } +}) + +module.exports = router From 5afd69b9313a676c993fc2e2c28414cbd3c3019f Mon Sep 17 00:00:00 2001 From: rkorsak Date: Wed, 26 Oct 2016 12:19:16 -0400 Subject: [PATCH 046/147] Added bootstrap in npm, moved public files --- app.js | 3 +++ package.json | 1 + public/{stylesheets => css}/style.css | 0 views/layout.jade | 8 ++++---- 4 files changed, 8 insertions(+), 4 deletions(-) rename public/{stylesheets => css}/style.css (100%) diff --git a/app.js b/app.js index b89b8f2..c404f48 100644 --- a/app.js +++ b/app.js @@ -39,6 +39,9 @@ app.use(bodyParser.json()) app.use(bodyParser.urlencoded({ extended: false })) app.use(cookieParser()) app.use(express.static(path.join(__dirname, 'public'))) +// Include the bootstrap package +app.use('/css', express.static(path.join(__dirname, 'node_modules/bootstrap/dist/css'))) +app.use('/js', express.static(path.join(__dirname, 'node_modules/bootstrap/dist/js'))) app.use(helmet()) app.use('/', index) diff --git a/package.json b/package.json index f77d2ff..c25218b 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ ], "dependencies": { "body-parser": "~1.13.2", + "bootstrap": "3.3.7", "cookie-parser": "~1.3.5", "csurf": "~1.8.3", "debug": "~2.2.0", diff --git a/public/stylesheets/style.css b/public/css/style.css similarity index 100% rename from public/stylesheets/style.css rename to public/css/style.css diff --git a/views/layout.jade b/views/layout.jade index f06aeda..c2e376d 100644 --- a/views/layout.jade +++ b/views/layout.jade @@ -18,11 +18,11 @@ head title=title meta(name='description', content='') meta(name='viewport', content='width=device-width, initial-scale=1') - link(rel='stylesheet', href='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css', integrity='sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7', crossorigin='anonymous') - link(rel='stylesheet', href='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap-theme.min.css', integrity='sha384-fLW2N01lMqjakBkx3l/M9EahuwpSfeNvV63J5ezn3uZzapT0u7EYsXMjQV+0En5r', crossorigin='anonymous') - link(rel='stylesheet', href='/stylesheets/style.css') + link(rel='stylesheet', href='/css/bootstrap.min.css') + link(rel='stylesheet', href='/css/bootstrap-theme.min.css') + link(rel='stylesheet', href='/css/style.css') script(src='https://code.jquery.com/jquery-2.2.0.min.js') - script(src='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js', integrity='sha384-0mSbJDEHialfmuBBQP6A4Qrprq5OVfW37PRR3j5ELqxss1yVqOtnepnHVP9aJ7xS', crossorigin='anonymous') + script(src='/js/bootstrap.min.js') body div.container block content From c44fc7db7d070d7805ac2bfacc6ffc305d9dcc87 Mon Sep 17 00:00:00 2001 From: rkorsak Date: Wed, 26 Oct 2016 13:43:56 -0400 Subject: [PATCH 047/147] Add Font Awesome --- app.js | 4 ++++ package.json | 1 + views/layout.jade | 1 + 3 files changed, 6 insertions(+) diff --git a/app.js b/app.js index c404f48..bb2fee4 100644 --- a/app.js +++ b/app.js @@ -42,6 +42,10 @@ app.use(express.static(path.join(__dirname, 'public'))) // Include the bootstrap package app.use('/css', express.static(path.join(__dirname, 'node_modules/bootstrap/dist/css'))) app.use('/js', express.static(path.join(__dirname, 'node_modules/bootstrap/dist/js'))) +// Include font awesome +app.use('/css', express.static(path.join(__dirname, 'node_modules/font-awesome/css'))) +app.use('/fonts', express.static(path.join(__dirname, 'node_modules/font-awesome/fonts'))) + app.use(helmet()) app.use('/', index) diff --git a/package.json b/package.json index c25218b..85b1cd5 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "debug": "~2.2.0", "ejs": "~2.3.3", "express": "~4.13.1", + "font-awesome": "4.7.0", "helmet": "~1.1.0", "jade": "~1.11.0", "lodash": "~4.1.0", diff --git a/views/layout.jade b/views/layout.jade index c2e376d..4ac7a91 100644 --- a/views/layout.jade +++ b/views/layout.jade @@ -20,6 +20,7 @@ head meta(name='viewport', content='width=device-width, initial-scale=1') link(rel='stylesheet', href='/css/bootstrap.min.css') link(rel='stylesheet', href='/css/bootstrap-theme.min.css') + link(rel='stylesheet', href='/css/font-awesome.min.css') link(rel='stylesheet', href='/css/style.css') script(src='https://code.jquery.com/jquery-2.2.0.min.js') script(src='/js/bootstrap.min.js') From 8719c749018249c360ffb5bfdc133285e60b1a21 Mon Sep 17 00:00:00 2001 From: rkorsak Date: Wed, 26 Oct 2016 16:17:13 -0400 Subject: [PATCH 048/147] Add favicon --- app.js | 2 +- public/favicon.ico | Bin 0 -> 1150 bytes 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 public/favicon.ico diff --git a/app.js b/app.js index bb2fee4..66382a9 100644 --- a/app.js +++ b/app.js @@ -33,7 +33,7 @@ app.set('views', path.join(__dirname, 'views')) app.set('view engine', 'jade') // uncomment after placing your favicon in /public -// app.use(favicon(path.join(__dirname, 'public', 'favicon.ico'))) +app.use(favicon(path.join(__dirname, 'public', 'favicon.ico'))) app.use(logger('dev')) app.use(bodyParser.json()) app.use(bodyParser.urlencoded({ extended: false })) diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..227fa2a4e2e1b86216001367d2856ff134c3e1a8 GIT binary patch literal 1150 zcmZQzU<5(|0R|wcz>vYhz#zuJz@P!dKp~(AL>x#lFaYILfs!CRoPb=W@lGKALLxp6 z3j?6O|3G{di01(DD<}QaFCE|i9`vFZhxiKZW?I*?jy7)d{=eg~_E z=^tns=(i8#V3@msdcR={A3|bC`fY$}b4bKqF#SWB{trxrFx Date: Wed, 26 Oct 2016 16:18:06 -0400 Subject: [PATCH 049/147] Update products API --- creditoffers/products.js | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/creditoffers/products.js b/creditoffers/products.js index a00e89f..68eb4a0 100644 --- a/creditoffers/products.js +++ b/creditoffers/products.js @@ -15,6 +15,7 @@ See the License for the specific language governing permissions and limitations var request = require('request') var debug = require('debug')('credit-offers:api-client') +var _ = require('lodash') /** * Contains all functions for interacting with the credit offer product listings API @@ -38,7 +39,7 @@ Products.prototype.getAll = function getAll (pagingOptions, callback) { var query = _.pick(pagingOptions, ['limit', 'offset']) this.client.sendRequest({ - url: '/credit-offers/products', + url: '/credit-offers/products/cards', useOAuth: true, method: 'GET', query: query @@ -46,15 +47,17 @@ Products.prototype.getAll = function getAll (pagingOptions, callback) { } /** - * Retrieve summary information on all consumer card products + * Retrieve detailed information on all card products of a specific type + * @param {string} cardType The type of card (BusinessCard, ConsumerCard) * @param {object} pagingOptions Optionally control the number of results and starting offset * in the result set */ -Products.prototype.getConsumerCards = function getConsumerCards (pagingOptions, callback) { +Products.prototype.getCards = function getCards (cardType, pagingOptions, callback) { + if (!cardType) { callback(new Error('A card type must be specified')) } var query = _.pick(pagingOptions, ['limit', 'offset']) this.client.sendRequest({ - url: '/credit-offers/products/consumer-cards', + url: '/credit-offers/products/cards/' + encodeURIComponent(cardType), useOAuth: true, method: 'GET', query: query @@ -63,13 +66,15 @@ Products.prototype.getConsumerCards = function getConsumerCards (pagingOptions, /** * Retrieve summary information on all products + * @param {string} cardType The type of card (BusinessCard, ConsumerCard) * @param {string} productId The ID of the consumer card product for which to retrieve details */ -Products.prototype.getConsumerCardDetail = function getConsumerCardDetail (productId, callback) { +Products.prototype.getCardDetail = function getCardDetail (cardType, productId, callback) { + if (!cardType) { callback(new Error('A card type must be specified')) } if (!productId) { callback(new Error('A product ID must be specified in order to retrieve product details')) } this.client.sendRequest({ - url: '/credit-offers/products/consumer-cards/' + encodeURIComponent(productId), + url: '/credit-offers/products/' + encodeURIComponent(cardType) + '/' + encodeURIComponent(productId), useOAuth: true, method: 'GET' }, callback) From 5aea26327ca4225f2207435b2aa8d61d5212a453 Mon Sep 17 00:00:00 2001 From: rkorsak Date: Wed, 26 Oct 2016 16:18:23 -0400 Subject: [PATCH 050/147] First major UI update for cards --- app.js | 2 +- public/css/style.css | 86 +++++++++++++++++++++++++++++++++++++++++++- routes/index.js | 52 ++++++++++++++++++++++----- views/index.jade | 40 ++++++++++++++++++--- views/layout.jade | 10 ++++-- 5 files changed, 173 insertions(+), 17 deletions(-) diff --git a/app.js b/app.js index 66382a9..0db3f46 100644 --- a/app.js +++ b/app.js @@ -48,7 +48,7 @@ app.use('/fonts', express.static(path.join(__dirname, 'node_modules/font-awesome app.use(helmet()) -app.use('/', index) +app.use('/', index(config.creditOffers)) app.use('/offers', offers(config.creditOffers)) // catch 404 and forward to error handler diff --git a/public/css/style.css b/public/css/style.css index 2aa72ff..731838e 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -15,9 +15,14 @@ See the License for the specific language governing permissions and limitations body { padding-bottom: 20px; - padding-top: 20px; + padding-top: 100px; font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; } +@media (min-width: 768px) { + body { + padding-top: 50px; } +} + .form-group.required > label:after { content: " *"; color: red; } @@ -44,3 +49,82 @@ body { width: 100px; } .product-offers .product-offer .primary-benefit { font-style: italic; } + +.cards .header { + background-color: #efefef; + padding-bottom: 10px; } + .cards .header .filters { + margin-top: 15px; } + .cards .card-info { + margin-top: 15px; } + +.card-info { + display: flex; + flex-flow: column nowrap; + border: 1px solid #ddd; + background-color: #fff; } + .card-info .card-title { + font-size: 1.5em; + font-weight: 600; } + .card-info .main-info { + display: flex; + flex-flow: row nowrap; } + .card-info .main-info .details { + padding: 20px; + flex-grow: 1; } + .card-info .main-info .apply { + text-align: center; + padding: 20px; + color: #555; + background-color: #eee; } + .card-info .additional-info { + display: flex; + flex-flow: row nowrap; + align-items: stretch; + background-color: #bbb; + box-shadow: inset 0 6px 6px -4px rgba(0,0,0,0.25); } + .card-info .additional-info > * { + flex-grow: 1; } + +.btn i { + margin-right: 10px; } + +/* Bootstrap overrides */ +.navbar-default { + border: 0; + background-image: none; + background-color: #03A9F4; } + .navbar-default .navbar-brand, + .navbar-default .navbar-brand:hover, + .navbar-default .navbar-brand:focus { + color: #fff; + text-shadow: none; } + .navbar-default .navbar-brand i { + margin-right: 10px; } + +.btn[disabled] { + cursor: default; } + +.btn-success { + background-image: none; + background-color: #8BC34A; + box-shadow: none; + border: none; + text-shadow: none; } + .btn-success:hover, .btn-success:focus { + background-image: none; + background-color: #9ace5d; + background-position: 0; } + +.btn-primary { + background-image: none; + background-color: #1976D2; + box-shadow: none; + border: none; + text-shadow: none; } + .btn-primary:hover, .btn-primary:focus { + background-image: none; + background-color: #2196F3; + background-position: 0; } + .btn-primary[disabled], .btn-primary[disabled]:hover, .btn-primary[disabled]:focus { + background-color: #1976D2; } diff --git a/routes/index.js b/routes/index.js index c476d43..79c1229 100644 --- a/routes/index.js +++ b/routes/index.js @@ -14,16 +14,50 @@ See the License for the specific language governing permissions and limitations */ var express = require('express') -var router = express.Router() +var CreditOffers = require('../creditoffers') +var oauth = require('../oauth') var csrf = require('csurf') -var csrfProtection = csrf({ cookie: true }) +var _ = require('lodash') -/* GET home page. */ -router.get('/', csrfProtection, function (req, res, next) { - res.render('index', { - csrfToken: req.csrfToken(), - title: 'Credit Offers Sample' +module.exports = function (options) { + var client = new CreditOffers(options.client, oauth(options.oauth)) + var csrfProtection = csrf({ cookie: true }) + var router = express.Router() + + // The supported card types + var cardTypes = [ + { + name: 'ConsumerCard', + display: 'Consumer Cards' + }, + { + name: 'BusinessCard', + display: 'Business Cards' + } + ] + + // How many products to pull at a time + var productCount = 20 + + /* GET home page. */ + router.get('/', csrfProtection, function (req, res, next) { + var requestedCardType = _.find(cardTypes, { name: req.query.cardType }) + if (!requestedCardType) { + res.redirect('/?cardType=' + cardTypes[0]) + return + } + + client.products.getCards(requestedCardType.name, { limit: productCount }, function (err, data) { + cards = _.get(data, 'products') + res.render('index', { + csrfToken: req.csrfToken(), + title: 'Credit Offers Reference App', + currentCardType: requestedCardType.name, + cardTypes: cardTypes, + cards: cards + }) + }) }) -}) -module.exports = router + return router +} diff --git a/views/index.jade b/views/index.jade index e0f3c75..4ee9b7e 100644 --- a/views/index.jade +++ b/views/index.jade @@ -13,11 +13,43 @@ extends ./layout +mixin cardInfo(card) + div.card-info + div.main-info + div.details + p.card-title= card.productDisplayName + img(src=card.images[0].url) + div.apply + a.btn.btn-lg.btn-success(href=card.applyNowLink) + i.fa.fa-lock(aria-hidden="true") + | APPLY NOW + p With CapitalOne® + div.additional-info + p Info 1 + p Info 2 + p Info 3 + + +block navbar-custom + button.navbar-right.btn.btn-md.navbar-btn.btn-success(role='button', data-toggle='modal', data-target='#customer-info') + i.btn-img.fa.fa-credit-card(aria-hidden="true") + | Find Pre-Qualified Offers + block content - div.jumbotron - h1 You Deserve a Better Credit Card! - p - button.btn.btn-lg.btn-success(role='button', data-toggle='modal', data-target='#customer-info') See Your Offers + div.cards + div.header + div.container + div.row + div.col-md-8 + h2 CapitalOne® Credit Cards + p These are some of the cards on offer from CapitalOne®. + div.filters.col-md-4 + div.btn-group(role="group", aria-label="Card Types") + each cardType in cardTypes + a.btn.btn-primary(href='/?cardType='+cardType.name, disabled=currentCardType===cardType.name)= cardType.display + div.container + each card in cards + +cardInfo(card) block modals div#customer-info.modal.fade(tabindex='-1', role='dialog') diff --git a/views/layout.jade b/views/layout.jade index 4ac7a91..3fb6c56 100644 --- a/views/layout.jade +++ b/views/layout.jade @@ -25,7 +25,13 @@ head script(src='https://code.jquery.com/jquery-2.2.0.min.js') script(src='/js/bootstrap.min.js') body - div.container - block content + nav.navbar.navbar-default.navbar-fixed-top + div.container + div.navbar-header + a.navbar-brand(href="/") + i.fa.fa-credit-card-alt(aria-hidden="true") + | CreditSite + block navbar-custom + block content block modals block scripts From f12a508dd7a3a26c8146cfabb6a39b79b0a3bced Mon Sep 17 00:00:00 2001 From: rkorsak Date: Wed, 26 Oct 2016 16:28:57 -0400 Subject: [PATCH 051/147] Fixed pagination query string in product api client --- creditoffers/products.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/creditoffers/products.js b/creditoffers/products.js index 68eb4a0..2a54e50 100644 --- a/creditoffers/products.js +++ b/creditoffers/products.js @@ -42,7 +42,7 @@ Products.prototype.getAll = function getAll (pagingOptions, callback) { url: '/credit-offers/products/cards', useOAuth: true, method: 'GET', - query: query + qs: query }, callback) } @@ -60,7 +60,7 @@ Products.prototype.getCards = function getCards (cardType, pagingOptions, callba url: '/credit-offers/products/cards/' + encodeURIComponent(cardType), useOAuth: true, method: 'GET', - query: query + qs: query }, callback) } From 2514fc9721e5d72e806e8810b105062844291348 Mon Sep 17 00:00:00 2001 From: rkorsak Date: Wed, 26 Oct 2016 16:29:09 -0400 Subject: [PATCH 052/147] UI adjustments --- public/css/style.css | 4 ++-- views/index.jade | 5 +---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/public/css/style.css b/public/css/style.css index 731838e..91e12ed 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -61,7 +61,7 @@ body { .card-info { display: flex; flex-flow: column nowrap; - border: 1px solid #ddd; + border: 1px solid #aaa; background-color: #fff; } .card-info .card-title { font-size: 1.5em; @@ -70,7 +70,7 @@ body { display: flex; flex-flow: row nowrap; } .card-info .main-info .details { - padding: 20px; + padding: 10px 20px 20px 20px; flex-grow: 1; } .card-info .main-info .apply { text-align: center; diff --git a/views/index.jade b/views/index.jade index 4ee9b7e..6eb8c14 100644 --- a/views/index.jade +++ b/views/index.jade @@ -23,11 +23,8 @@ mixin cardInfo(card) a.btn.btn-lg.btn-success(href=card.applyNowLink) i.fa.fa-lock(aria-hidden="true") | APPLY NOW - p With CapitalOne® + p with CapitalOne® div.additional-info - p Info 1 - p Info 2 - p Info 3 block navbar-custom From 4b4845a6ed8f7ad16a7eb8c0a9ea34ce8d027593 Mon Sep 17 00:00:00 2001 From: rkorsak Date: Wed, 26 Oct 2016 16:35:59 -0400 Subject: [PATCH 053/147] UI fixes and small update to prequal results --- public/css/style.css | 9 +++++---- routes/index.js | 2 +- views/index.jade | 23 +++++++++++------------ views/offers.jade | 19 ++++++++++--------- 4 files changed, 27 insertions(+), 26 deletions(-) diff --git a/public/css/style.css b/public/css/style.css index 91e12ed..f40c644 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -50,14 +50,15 @@ body { .product-offers .product-offer .primary-benefit { font-style: italic; } -.cards .header { +.header { background-color: #efefef; padding-bottom: 10px; } - .cards .header .filters { - margin-top: 15px; } - .cards .card-info { + .header .filters { margin-top: 15px; } +.cards .card-info { + margin-top: 15px; } + .card-info { display: flex; flex-flow: column nowrap; diff --git a/routes/index.js b/routes/index.js index 79c1229..fef1bab 100644 --- a/routes/index.js +++ b/routes/index.js @@ -43,7 +43,7 @@ module.exports = function (options) { router.get('/', csrfProtection, function (req, res, next) { var requestedCardType = _.find(cardTypes, { name: req.query.cardType }) if (!requestedCardType) { - res.redirect('/?cardType=' + cardTypes[0]) + res.redirect('/?cardType=' + cardTypes[0].name) return } diff --git a/views/index.jade b/views/index.jade index 6eb8c14..0e77e4e 100644 --- a/views/index.jade +++ b/views/index.jade @@ -33,18 +33,17 @@ block navbar-custom | Find Pre-Qualified Offers block content - div.cards - div.header - div.container - div.row - div.col-md-8 - h2 CapitalOne® Credit Cards - p These are some of the cards on offer from CapitalOne®. - div.filters.col-md-4 - div.btn-group(role="group", aria-label="Card Types") - each cardType in cardTypes - a.btn.btn-primary(href='/?cardType='+cardType.name, disabled=currentCardType===cardType.name)= cardType.display - div.container + div.header + div.container + div.row + div.col-md-8 + h2 CapitalOne® Credit Cards + p These are some of the cards on offer from CapitalOne®. + div.filters.col-md-4 + div.btn-group(role="group", aria-label="Card Types") + each cardType in cardTypes + a.btn.btn-primary(href='/?cardType='+cardType.name, disabled=currentCardType===cardType.name)= cardType.display + div.cards.container each card in cards +cardInfo(card) diff --git a/views/offers.jade b/views/offers.jade index 13c5d6f..566a762 100644 --- a/views/offers.jade +++ b/views/offers.jade @@ -14,15 +14,16 @@ extends ./layout block content - p.offer-summary - if error - span.error= error - else if isPrequalified - h1 You prequalify for the following offers: - else if products && products.length - h1 We're sorry, we can't prequalify you based on the information that you provided, but you can check out these great offers: - else - h1 We weren't able to find any offers + div.header + div.container + if error + span.error= error + else if isPrequalified + h2 You prequalify for the following offers: + else if products && products.length + h2 We're sorry, we can't prequalify you based on the information that you provided, but you can check out these great offers: + else + h2 We weren't able to find any offers if products div.product-offers From d99d571843ad3061707f0da932be79183775d954 Mon Sep 17 00:00:00 2001 From: rkorsak Date: Fri, 28 Oct 2016 13:36:41 -0400 Subject: [PATCH 054/147] Simplified and enhanced product generation --- mock_api/mockproducts.js | 172 ++++++++++++++++++++------------------- 1 file changed, 90 insertions(+), 82 deletions(-) diff --git a/mock_api/mockproducts.js b/mock_api/mockproducts.js index 4deb572..93dcbcd 100644 --- a/mock_api/mockproducts.js +++ b/mock_api/mockproducts.js @@ -3,97 +3,103 @@ var format = require('util').format var moment = require('moment') var debug = require('debug')('credit-offers_mock_api:mockproducts') -function numberFormat (formatString, max, sampleSize) { - return function () { - var nums = _.sampleSize(_.range(max), _.random(sampleSize)) - return _.map(nums, function (num) { return format(formatString, num)}) - } +function multiSample (items, maxCount) { + return _.sampleSize(items, _.random(maxCount)) } -// Simple constant values to populate in each product -var constants = { - "creditRating": [], - "balanceTransferFeeDescription": "", - "introBalanceTransferAPRDescription": "", - "balanceTransferAPRDescription": "", - "introPurchaseAPRDescription": "", - "purchaseAPRDescription": "", - "annualMembershipFeeDescription": "", - "rewardsRateDescription": "", - "foreignTransactionFeeDescription": "", - "fraudCoverageDescription": "", - "latePaymentDescription": "", - "penaltyAPRDescription": "", - "cashAdvanceFeeDescription": "", - "cashAdvanceAPRDescription": "", - "generalDescription": "", - "promotionalDescriptions": [] +function numberFormat (formatString, max, sampleSize) { + var nums = multiSample(_.range(max), sampleSize) + return _.map(nums, _.partial(format, formatString)) } // Dynamic product properties to be built per product -var customProps = [ - // Set up randomized props - { - activeFrom: function () { - return moment().startOf('day').subtract(_.random(5), 'years').format() - }, - activeTo: function() { - return moment().startOf('day').add(_.random(1,5), 'years').format() - }, - applyNowLink: function (product) { - return format('https://capitalone.com?fake=applyNowLink&productId=%s', product.productId) - }, - brandName: _.partial(_.sample, ['Cool Brand', 'Great Brand', 'Awesome Brand']), - categoryTags: numberFormat('Tag %s', 100, 5), - productKeywords: numberFormat('Keyword %s', 100, 5), - marketingCopy: function () { return [] }, - processingNetwork: _.partial(_.sample, ['Visa', 'MasterCard']), - rewardsType: _.partial(_.sample, [null, 'Miles', 'Points']), - primaryBenefitDescription: function () { - return format('Earn unlimited %d miles per dollar on every purchase. Plus, earn %d0,000 bonus miles once you spend $%d,000 on purchases within the first %d months.', _.random(1,3), _.random(2,5), _.random(1,3), _.random(1,5)) - } - }, - // Set up props that rely on the previous pass - { - productDisplayName: function (product) { - return format('Fake %s %s %s', product.productType, product.processingNetwork, product.productId) - }, - publishedDate: function (product) { - return moment(product.activeFrom).subtract(_.random(1, 20), 'days').format() - } - }, - // Finally, set up the images (which require the display name from the previous step) - { - images: function (product) { - var filename = _.sample([ +var randomizeProduct = function (product) { + var productDisplayName = format('Fake %s %s %s', product.productType, processingNetwork, product.productId), + activeFrom = moment().startOf('day').subtract(_.random(5), 'years'), + processingNetwork = _.sample(['Visa', 'MasterCard']), + imageFile = _.sample([ 'JB16760-GenericEMV-Venture-EMV-flat-244x154-06-15.png', 'JB16760-Generic-VentureOne-EMV-flat-244x154-06-15.png', 'JB16760-MC-Blue-Steel-EMV-flat-244x154-06-15.png', 'www-venture-visa-sig-flat-9-14.png' - ]) - return [{ - height: 154, - width: 244, - alternateText: format('Apply now for the %s Card', product.productDisplayName), - url: 'http://localhost:3002/images/' + filename, - imageType: 'CardName' - }] - } - } -] + ]), + initialFee = _.random(0, 100), + fullFee = initialFee + _.random(50), + initialApr = _.random(0, 50), + fullAprLow = initialApr + _.random(9), + fullAprLowFraction = _.random(99), + fullAprHigh = fullAprLow + 10 + _.random(9), + fullAprHighFraction = _.random(99), + aprMonthLimit = _.random(2, 10) -// Apply a single set of dynamic properties to a product -function applyProps (product, propSpecs) { - var newProps = _.map(propSpecs, function (propFn, propName) { - return [propName, propFn(product)] - }) - return _.defaults(product, _.fromPairs(newProps)) -} - -// Add all dynamic properties to a product -function addProps (product) { - var withConstants = _.defaults(product, constants) - return _.reduce(customProps, applyProps, withConstants) + var newValues = { + productDisplayName: productDisplayName, + activeFrom: activeFrom.format(), + activeTo: activeFrom.add(_.random(6, 12), 'years').format(), + publishedDate: activeFrom.subtract(_.random(1, 20), 'days').format(), + applyNowLink: format('https://capitalone.com?fake=applyNowLink&productId=%s', product.productId), + brand: _.sample(['Cool Brand', 'Great Brand', 'Awesome Brand']), + images: [{ + height: 154, + width: 244, + alternateText: format('Apply now for the %s Card', productDisplayName), + url: 'http://localhost:3002/images/' + filename, + imageType: 'CardName' + }], + categoryTags: multiSample([ + 'Rewards (travel rewards, airlines)', + 'Excellent', + 'Good', + 'Capital One', + 'MasterCard', + 'Low Intro Rate', + 'Zero Percent', + 'Low Interest' + ], 4), + productKeywords: multiSample([ + 'Capital One', + 'Card', + 'Credit', + 'Credit Card', + 'VentureOne', + 'Rewards' + ], 3), + marketingCopy: multiSample([ + 'Enjoy a one-time bonus of 20,000 miles once you spend $1,000 on purchases within 3 months of approval, equal to $200 in travel', + 'Earn unlimited 1.25 miles per dollar on every purchase, every day and pay no annual fee', + 'Fly any airline, stay at any hotel, anytime' + ], 3), + processingNetwork: processingNetwork, + creditRating: multiSample([ + 'Excellent', + 'Good', + 'Average', + 'Rebuilding' + ], 2), + rewardsType: _.sample([null, 'Miles', 'Points']), + primaryBenefitDescription: format('Earn unlimited %d miles per dollar on every purchase. Plus, earn %d0,000 bonus miles once you spend $%d,000 on purchases within the first %d months.', _.random(1,3), _.random(2,5), _.random(1,3), _.random(1,5)), + balanceTransferFeeDescription: format('$%d', initialFee), + introBalanceTransferAPRDescription: format('%d%%', initialApr), + balanceTransferAPRDescription: format('%d%% intro APR for %d months; %d.%d%% to %d.%d%% variable APR after that', + initialApr, + aprMonthLimit, + fullAprLow, fullAprLowFraction, + fullAprHigh, fullAprHighFraction), + purchaseAPRDescription: format('%d.%d%% to %d.%d%% variable APR after the first %d months', + fullAprLow, fullAprLowFraction, + fullAprHigh, fullAprHighFraction, + aprMonthLimit), + annualMembershipFeeDescription: format('$%d intro for first year; $%d after that', initialFee, fullFee), + rewardsRateDescription: '', + foreignTransactionFeeDescription: '', + fraudCoverageDescription: '', + latePaymentDescription: '', + penaltyAPRDescription: '', + cashAdvanceAPRDescription: format('%d.%d%% (Variable)', fullAprHigh, fullAprHighFraction), + generalDescription: '', + promotionalDescriptions: [] + } + return _.defaults(product, newValues) } // Generate a collection of fake products based on a spec @@ -102,9 +108,11 @@ function generate (spec) { var totalCount = _.sum(_.values(spec)) var ids = _.range(1, totalCount + 1) var productTypes = _(spec) + // Generate the appropriate number of each product type .flatMap(function (count, productType) { return _().range(count).map(() => productType).value() }) + // Randomize the categories to distribute them evenly across IDs .shuffle() var startingProducts = productTypes.zipWith(ids, function (productType, id) { @@ -115,7 +123,7 @@ function generate (spec) { }) return startingProducts - .map(addProps) + .map(randomizeProduct) .sortBy(function (product) { return parseInt(product.productId) }) .value() } From 2a5f0f0803afb9c57316fd23e1a8747a6933bffc Mon Sep 17 00:00:00 2001 From: rkorsak Date: Fri, 28 Oct 2016 16:36:51 -0400 Subject: [PATCH 055/147] Update mock API products --- mock_api/mockproducts.js | 59 +++++++++++++++++++++++++++++++--------- mock_api/package.json | 1 + 2 files changed, 47 insertions(+), 13 deletions(-) diff --git a/mock_api/mockproducts.js b/mock_api/mockproducts.js index 93dcbcd..964def7 100644 --- a/mock_api/mockproducts.js +++ b/mock_api/mockproducts.js @@ -2,9 +2,14 @@ var _ = require('lodash') var format = require('util').format var moment = require('moment') var debug = require('debug')('credit-offers_mock_api:mockproducts') +var loremIpsum = require('lorem-ipsum') -function multiSample (items, maxCount) { - return _.sampleSize(items, _.random(maxCount)) +function multiSample (items, minCount, maxCount) { + if (maxCount === undefined) { + maxCount = minCount + minCount = 0 + } + return _.sampleSize(items, _.random(minCount, maxCount)) } function numberFormat (formatString, max, sampleSize) { @@ -14,15 +19,16 @@ function numberFormat (formatString, max, sampleSize) { // Dynamic product properties to be built per product var randomizeProduct = function (product) { - var productDisplayName = format('Fake %s %s %s', product.productType, processingNetwork, product.productId), - activeFrom = moment().startOf('day').subtract(_.random(5), 'years'), + var activeFrom = moment().startOf('day').subtract(_.random(5), 'years'), processingNetwork = _.sample(['Visa', 'MasterCard']), + productDisplayName = format('Fake %s %s %s', product.productType, processingNetwork, product.productId), imageFile = _.sample([ 'JB16760-GenericEMV-Venture-EMV-flat-244x154-06-15.png', 'JB16760-Generic-VentureOne-EMV-flat-244x154-06-15.png', 'JB16760-MC-Blue-Steel-EMV-flat-244x154-06-15.png', 'www-venture-visa-sig-flat-9-14.png' ]), + // Fees & APR initialFee = _.random(0, 100), fullFee = initialFee + _.random(50), initialApr = _.random(0, 50), @@ -30,7 +36,37 @@ var randomizeProduct = function (product) { fullAprLowFraction = _.random(99), fullAprHigh = fullAprLow + 10 + _.random(9), fullAprHighFraction = _.random(99), - aprMonthLimit = _.random(2, 10) + aprMonthLimit = _.random(2, 10), + // Mile rewards + milesPerDollar = _.random(2, 4), + bonusMiles = format('%d0,000', _.random(1, 5)), + rewardSpendingMin = format('%d,000', _.random(1, 3)), + rewardMonthLimit = _.random(3, 10).toString(), + // Marketing copy + customMarketingCopy = [ + format('Enjoy a one-time bonus of %d miles once you spend $%s on purchases within %d months of approval, equal to $200 in travel', + bonusMiles, rewardSpendingMin, rewardMonthLimit), + format('Earn unlimited %d miles per dollar on every purchase, every day and pay no annual fee', milesPerDollar), + 'Fly any airline, stay at any hotel, anytime', + 'Complies with the standard expected size for a credit card, guaranteeing a snug fit in any wallet', + 'Impress your friends by pretending your new card is a tiny skateboard, and using your fingers as a tiny person to perform cool tricks', + 'Collect multiple cards and build your very own house of cards', + 'Explore the world! Accepted in over 400 countries', + 'Now you can own anything, anytime, anywhere!', + 'Get a head start on your bucket list', + 'Keep your credit going in the right direction', + 'Official credit card of the Milwaukee Corn Dogs', + 'Earn bonus points on all purchases from Jake\'s Smoothie Hut', + 'Use it to buy something nice for your mother. She deserves it after all you\'ve put her through' + ], + randomMarketingCopy = _.map(_.range(15), function () { + return loremIpsum({ + count: _.random(12, 50), + units: 'words', + format: 'plain' + }) + }), + marketingCopy = _.concat(customMarketingCopy, randomMarketingCopy) var newValues = { productDisplayName: productDisplayName, @@ -43,7 +79,7 @@ var randomizeProduct = function (product) { height: 154, width: 244, alternateText: format('Apply now for the %s Card', productDisplayName), - url: 'http://localhost:3002/images/' + filename, + url: 'http://localhost:3002/images/' + imageFile, imageType: 'CardName' }], categoryTags: multiSample([ @@ -64,11 +100,7 @@ var randomizeProduct = function (product) { 'VentureOne', 'Rewards' ], 3), - marketingCopy: multiSample([ - 'Enjoy a one-time bonus of 20,000 miles once you spend $1,000 on purchases within 3 months of approval, equal to $200 in travel', - 'Earn unlimited 1.25 miles per dollar on every purchase, every day and pay no annual fee', - 'Fly any airline, stay at any hotel, anytime' - ], 3), + marketingCopy: multiSample(marketingCopy, 3, 10), processingNetwork: processingNetwork, creditRating: multiSample([ 'Excellent', @@ -77,7 +109,8 @@ var randomizeProduct = function (product) { 'Rebuilding' ], 2), rewardsType: _.sample([null, 'Miles', 'Points']), - primaryBenefitDescription: format('Earn unlimited %d miles per dollar on every purchase. Plus, earn %d0,000 bonus miles once you spend $%d,000 on purchases within the first %d months.', _.random(1,3), _.random(2,5), _.random(1,3), _.random(1,5)), + primaryBenefitDescription: format('Earn unlimited %d miles per dollar on every purchase. Plus, earn %s bonus miles once you spend $%s on purchases within the first %d months.', + milesPerDollar, bonusMiles, rewardSpendingMin, rewardMonthLimit), balanceTransferFeeDescription: format('$%d', initialFee), introBalanceTransferAPRDescription: format('%d%%', initialApr), balanceTransferAPRDescription: format('%d%% intro APR for %d months; %d.%d%% to %d.%d%% variable APR after that', @@ -90,7 +123,7 @@ var randomizeProduct = function (product) { fullAprHigh, fullAprHighFraction, aprMonthLimit), annualMembershipFeeDescription: format('$%d intro for first year; $%d after that', initialFee, fullFee), - rewardsRateDescription: '', + rewardsRateDescription: format(''), foreignTransactionFeeDescription: '', fraudCoverageDescription: '', latePaymentDescription: '', diff --git a/mock_api/package.json b/mock_api/package.json index 01d4c7f..44c605b 100644 --- a/mock_api/package.json +++ b/mock_api/package.json @@ -12,6 +12,7 @@ "express": "~4.13.1", "jade": "^1.11.0", "lodash": "4.16.4", + "lorem-ipsum": "1.0.3", "moment": "2.15.2", "morgan": "~1.6.1", "node-uuid": "1.4.7", From d4066d92f48b3d372b715b97963d4e4b128dc80d Mon Sep 17 00:00:00 2001 From: rkorsak Date: Fri, 28 Oct 2016 16:37:31 -0400 Subject: [PATCH 056/147] Run products through a view model before displaying --- public/css/style.css | 4 +++ routes/index.js | 3 +- routes/offers.js | 9 +++++- viewmodels/index.js | 22 ++++++++++++++ viewmodels/preQualProduct.js | 40 ++++++++++++++++++++++++ viewmodels/product.js | 59 ++++++++++++++++++++++++++++++++++++ views/error.jade | 9 +++--- views/index.jade | 33 ++++++++++---------- views/layout.jade | 12 ++++++++ views/offers.jade | 35 ++++++++++++--------- 10 files changed, 189 insertions(+), 37 deletions(-) create mode 100644 viewmodels/index.js create mode 100644 viewmodels/preQualProduct.js create mode 100644 viewmodels/product.js diff --git a/public/css/style.css b/public/css/style.css index f40c644..c989a13 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -78,6 +78,10 @@ body { padding: 20px; color: #555; background-color: #eee; } + .card-info .summary { + display: flex; + flex-flow: row nowrap; + align-items: flex-start; } .card-info .additional-info { display: flex; flex-flow: row nowrap; diff --git a/routes/index.js b/routes/index.js index fef1bab..f9510c5 100644 --- a/routes/index.js +++ b/routes/index.js @@ -18,6 +18,7 @@ var CreditOffers = require('../creditoffers') var oauth = require('../oauth') var csrf = require('csurf') var _ = require('lodash') +var productViewModel = require('../viewmodels').product module.exports = function (options) { var client = new CreditOffers(options.client, oauth(options.oauth)) @@ -48,7 +49,7 @@ module.exports = function (options) { } client.products.getCards(requestedCardType.name, { limit: productCount }, function (err, data) { - cards = _.get(data, 'products') + cards = _.map(data.products, productViewModel) res.render('index', { csrfToken: req.csrfToken(), title: 'Credit Offers Reference App', diff --git a/routes/offers.js b/routes/offers.js index 3e43f2d..fa4d814 100644 --- a/routes/offers.js +++ b/routes/offers.js @@ -21,6 +21,7 @@ var csrf = require('csurf') var CreditOffers = require('../creditoffers') var oauth = require('../oauth') var debug = require('debug')('credit-offers:offers') +var productViewModel = require('../viewmodels').preQualProduct module.exports = function (options) { var router = express.Router() @@ -49,11 +50,17 @@ module.exports = function (options) { client.prequalification.create(customerInfo, function (err, response) { if (err) { return next(err) } + var apiProducts = response.products || [] + var productViewModels = _(apiProducts) + .sortBy('priority') // Display in the priority order given by the API + .map(productViewModel) // Transform to a view model for easier display + .value() + var viewModel = { title: 'Credit Offers', isPrequalified: response.isPrequalified, prequalificationId: response.prequalificationId, - products: response.products && _.sortBy(response.products, 'priority') + products: productViewModels } res.render('offers', viewModel) diff --git a/viewmodels/index.js b/viewmodels/index.js new file mode 100644 index 0000000..f92c362 --- /dev/null +++ b/viewmodels/index.js @@ -0,0 +1,22 @@ +/* +Copyright 2016 Capital One Services, LLC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and limitations under the License. +*/ + +var product = require('./product') +var preQualProduct = require('./preQualProduct') + +module.exports = { + product: product, + preQualProduct: preQualProduct +} diff --git a/viewmodels/preQualProduct.js b/viewmodels/preQualProduct.js new file mode 100644 index 0000000..de90ba3 --- /dev/null +++ b/viewmodels/preQualProduct.js @@ -0,0 +1,40 @@ +/* +Copyright 2016 Capital One Services, LLC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and limitations under the License. +*/ + +/** @module Defines a consistent model for displaying pre-qualification products, which contain less info than normal products **/ + +var _ = require('lodash') + +module.exports = function preQualProduct (apiProduct) { + var viewModel = _.pick(apiProduct, [ + 'productId', + 'productDisplayName', + 'tier', + 'terms', + 'additionalInformationUrl' + ]) + + viewModel.images = { + cardName: _.find(apiProduct.images, { imageType: 'CardName' }) + } + // Normalize to the keys used by the products API + viewModel.primaryBenefitDescription = _.get(apiProduct, 'terms.primaryBenefit') + viewModel.purchaseAPRDescription = _.get(apiProduct, 'terms.purchaseAprTerms') + viewModel.balanceTransferAPRDescription = _.get(apiProduct, 'terms.balanceTransferTerms') + viewModel.annualMembershipFeeDescription = _.get(apiProduct, 'terms.annualMembershipFeeTerms') + viewModel.applyNowLink = apiProduct.applicationUrl + + return viewModel +} diff --git a/viewmodels/product.js b/viewmodels/product.js new file mode 100644 index 0000000..3a50a89 --- /dev/null +++ b/viewmodels/product.js @@ -0,0 +1,59 @@ +/* +Copyright 2016 Capital One Services, LLC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and limitations under the License. +*/ + +/** @module Defines a consistent model for displaying products from the API **/ + +var _ = require('lodash') + +module.exports = function product (apiProduct) { + var viewModel = _.pick(apiProduct, [ + 'productId', + 'productDisplayName', + 'activeFrom', + 'activeTo', + 'publishedDate', + 'applyNowLink', + 'productType', + 'brand', + 'categoryTags', + 'productKeywords', + 'marketingCopy', + 'processingNetwork', + 'creditRating', + 'rewardsType', + 'primaryBenefitDescription', + 'balanceTransferAPRDescription', + 'introPurchaseAPRDescription', + 'purchaseAPRDescription', + 'annualMembershipFeeDescription', + 'rewardsRateDescription', + 'foreignTransactionFeeDescription', + 'fraudCoverageDescription', + 'latePaymentDescription', + 'penaltyAPRDescription', + 'cashAdvanceFeeDescription', + 'cashAdvanceAPRDescription', + 'generalDescription', + 'promotionalDescriptions' + ]) + + viewModel.images = { + cardName: _.find(apiProduct.images, { imageType: 'CardName' }) + } + + viewModel.additionalInformationUrl = _.get(apiProduct, 'links.self.href') + + return viewModel +} diff --git a/views/error.jade b/views/error.jade index f46cb80..0077382 100644 --- a/views/error.jade +++ b/views/error.jade @@ -14,7 +14,8 @@ extends ./layout.jade block content - h1=message - h2=error.status - pre - error.stack + div.container + h1=message + h2=error.status + if error.stack + pre= error.stack diff --git a/views/index.jade b/views/index.jade index 0e77e4e..f4b80c1 100644 --- a/views/index.jade +++ b/views/index.jade @@ -11,21 +11,7 @@ //- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. //- See the License for the specific language governing permissions and limitations under the License. -extends ./layout - -mixin cardInfo(card) - div.card-info - div.main-info - div.details - p.card-title= card.productDisplayName - img(src=card.images[0].url) - div.apply - a.btn.btn-lg.btn-success(href=card.applyNowLink) - i.fa.fa-lock(aria-hidden="true") - | APPLY NOW - p with CapitalOne® - div.additional-info - +extends ./layout.jade block navbar-custom button.navbar-right.btn.btn-md.navbar-btn.btn-success(role='button', data-toggle='modal', data-target='#customer-info') @@ -45,7 +31,20 @@ block content a.btn.btn-primary(href='/?cardType='+cardType.name, disabled=currentCardType===cardType.name)= cardType.display div.cards.container each card in cards - +cardInfo(card) + div.card-info + div.main-info + div.details + p.card-title= card.productDisplayName + div.summary + +cardNameImage(card) + div.marketing-copy + ul + each copyLine in card.marketingCopy + li= copyLine + div.apply + +applyButton(card) + p with CapitalOne® + div.additional-info block modals div#customer-info.modal.fade(tabindex='-1', role='dialog') @@ -59,7 +58,7 @@ block modals × h4.modal-title Tell us a little about yourself div.modal-body - include ./includes/customer-form.jade + include ./includes/customer-form div.modal-footer button.btn.btn-default(type='button', data-dismiss='modal') | Close diff --git a/views/layout.jade b/views/layout.jade index 3fb6c56..8bc71a8 100644 --- a/views/layout.jade +++ b/views/layout.jade @@ -11,6 +11,18 @@ //- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. //- See the License for the specific language governing permissions and limitations under the License. +//- Common Card mixins +mixin cardNameImage(card) + if card.images && card.images.cardName + img.card-name(src=card.images.cardName.url, alt=card.images.cardName.alternateText) + else + img.card-name(src='/images/default-card.png') + +mixin applyButton(card) + a.btn.btn-lg.btn-success(href=card.applyNowLink) + i.fa.fa-lock(aria-hidden="true") + | APPLY NOW + doctype html(lang='en') head meta(charset='utf-8') diff --git a/views/offers.jade b/views/offers.jade index 566a762..5879079 100644 --- a/views/offers.jade +++ b/views/offers.jade @@ -24,20 +24,27 @@ block content h2 We're sorry, we can't prequalify you based on the information that you provided, but you can check out these great offers: else h2 We weren't able to find any offers - - if products - div.product-offers - each product in products - div.product-offer.media - div.media-left - img.product-image( - src = product.images[0].url - alt = product.images[0].alternateText - ) - div.media-body - h4.media-heading= product.productDisplayName - if product.terms && product.terms.primaryBenefit - div.primary-benefit= product.terms.primaryBenefit + + div.cards.container + each product in products + div.card-info + div.main-info + div.details + p.card-title= card.productDisplayName + div.summary + +cardNameImage(card) + ul.marketingCopy + if card.primaryBenefitDescription + li= card.primaryBenefitDescription + if card.purchaseAPRDescription + li= card.purchaseAPRDescription + if card.balanceTransferAPRDescription + li= card.balanceTransferAPRDescription + if card.annualMembershipFeeDescription + li= card.annualMembershipFeeDescription + div.apply + +applyButton(card) + p with CapitalOne® block scripts script. From befd2d510b2b65ead0afc44af4e35d7d1eeee265 Mon Sep 17 00:00:00 2001 From: rkorsak Date: Fri, 28 Oct 2016 17:50:43 -0400 Subject: [PATCH 057/147] Fix a bad string in mock products --- mock_api/mockproducts.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mock_api/mockproducts.js b/mock_api/mockproducts.js index 964def7..bbdb90f 100644 --- a/mock_api/mockproducts.js +++ b/mock_api/mockproducts.js @@ -44,7 +44,7 @@ var randomizeProduct = function (product) { rewardMonthLimit = _.random(3, 10).toString(), // Marketing copy customMarketingCopy = [ - format('Enjoy a one-time bonus of %d miles once you spend $%s on purchases within %d months of approval, equal to $200 in travel', + format('Enjoy a one-time bonus of %s miles once you spend $%s on purchases within %d months of approval, equal to $200 in travel', bonusMiles, rewardSpendingMin, rewardMonthLimit), format('Earn unlimited %d miles per dollar on every purchase, every day and pay no annual fee', milesPerDollar), 'Fly any airline, stay at any hotel, anytime', From 8a5aaf458602f781f28575b41d1aa8b4319fd382 Mon Sep 17 00:00:00 2001 From: rkorsak Date: Fri, 28 Oct 2016 17:50:57 -0400 Subject: [PATCH 058/147] Change the marketing copy sample size --- mock_api/mockproducts.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mock_api/mockproducts.js b/mock_api/mockproducts.js index bbdb90f..4ac0ea3 100644 --- a/mock_api/mockproducts.js +++ b/mock_api/mockproducts.js @@ -100,7 +100,7 @@ var randomizeProduct = function (product) { 'VentureOne', 'Rewards' ], 3), - marketingCopy: multiSample(marketingCopy, 3, 10), + marketingCopy: multiSample(marketingCopy, 1, 10), processingNetwork: processingNetwork, creditRating: multiSample([ 'Excellent', From d652e309e325fb4d9190b6fb1d45b90b94605619 Mon Sep 17 00:00:00 2001 From: rkorsak Date: Fri, 28 Oct 2016 17:51:23 -0400 Subject: [PATCH 059/147] Style updates, collapsible marketing copy --- public/css/style.css | 34 ++++++++++++++++++--------------- viewmodels/preQualProduct.js | 7 +++++++ viewmodels/product.js | 5 ++++- views/index.jade | 12 +++++------- views/layout.jade | 37 +++++++++++++++++++++++++++++++++++- views/offers.jade | 12 ++---------- 6 files changed, 73 insertions(+), 34 deletions(-) diff --git a/public/css/style.css b/public/css/style.css index c989a13..2e2d43c 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -35,21 +35,6 @@ body { border-bottom: 1px solid #ccc; } -.product-offers { - margin-top: 20px; } - .product-offers .product-offer { - width: 500px; - margin-left: auto; - margin-right: auto; - padding: 20px; - border: 1px solid #ccc; - border-radius: 5px; - box-shadow: #ddd 0 2px; } - .product-offers .product-offer .product-image { - width: 100px; } - .product-offers .product-offer .primary-benefit { - font-style: italic; } - .header { background-color: #efefef; padding-bottom: 10px; } @@ -78,6 +63,8 @@ body { padding: 20px; color: #555; background-color: #eee; } + .card-info .main-info .apply > .btn { + margin-bottom: 5px; } .card-info .summary { display: flex; flex-flow: row nowrap; @@ -91,6 +78,23 @@ body { .card-info .additional-info > * { flex-grow: 1; } +.marketing-copy > ul:first-child { + margin-bottom: 0; } +.marketing-copy ul.collapse { + margin-top: 0; } +.marketing-copy a.show-hide { + display: inline-block; + margin-top: 10px; + margin-left: 40px; } + .marketing-copy a.show-hide:focus { + text-decoration: none; } + .marketing-copy .collapse + a.show-hide::after, .marketing-copy .collapsing + a.show-hide::after { + font-family: FontAwesome; + content: " \f078"; } + .marketing-copy .collapse.in + a.show-hide::after { + font-family: FontAwesome; + content: " \f077"; } + .btn i { margin-right: 10px; } diff --git a/viewmodels/preQualProduct.js b/viewmodels/preQualProduct.js index de90ba3..950e1fa 100644 --- a/viewmodels/preQualProduct.js +++ b/viewmodels/preQualProduct.js @@ -36,5 +36,12 @@ module.exports = function preQualProduct (apiProduct) { viewModel.annualMembershipFeeDescription = _.get(apiProduct, 'terms.annualMembershipFeeTerms') viewModel.applyNowLink = apiProduct.applicationUrl + var marketingCopy = _.compact([ + viewModel.primaryBenefitDescription, + viewModel.balanceTransferAPRDescription + ]) + viewModel.mainMarketingCopy = _.take(marketingCopy, 2) + viewModel.extraMarketingCopy = _.drop(marketingCopy, 2) + return viewModel } diff --git a/viewmodels/product.js b/viewmodels/product.js index 3a50a89..83cb689 100644 --- a/viewmodels/product.js +++ b/viewmodels/product.js @@ -29,7 +29,6 @@ module.exports = function product (apiProduct) { 'brand', 'categoryTags', 'productKeywords', - 'marketingCopy', 'processingNetwork', 'creditRating', 'rewardsType', @@ -55,5 +54,9 @@ module.exports = function product (apiProduct) { viewModel.additionalInformationUrl = _.get(apiProduct, 'links.self.href') + var marketingCopy = apiProduct.marketingCopy || [] + viewModel.mainMarketingCopy = _.take(marketingCopy, 2) + viewModel.extraMarketingCopy = _.drop(marketingCopy, 2) + return viewModel } diff --git a/views/index.jade b/views/index.jade index f4b80c1..fc25d74 100644 --- a/views/index.jade +++ b/views/index.jade @@ -26,9 +26,10 @@ block content h2 CapitalOne® Credit Cards p These are some of the cards on offer from CapitalOne®. div.filters.col-md-4 - div.btn-group(role="group", aria-label="Card Types") - each cardType in cardTypes - a.btn.btn-primary(href='/?cardType='+cardType.name, disabled=currentCardType===cardType.name)= cardType.display + div.pull-right + div.btn-group(role="group", aria-label="Card Types") + each cardType in cardTypes + a.btn.btn-primary(href='/?cardType='+cardType.name, disabled=currentCardType===cardType.name)= cardType.display div.cards.container each card in cards div.card-info @@ -37,10 +38,7 @@ block content p.card-title= card.productDisplayName div.summary +cardNameImage(card) - div.marketing-copy - ul - each copyLine in card.marketingCopy - li= copyLine + +marketingCopy(card) div.apply +applyButton(card) p with CapitalOne® diff --git a/views/layout.jade b/views/layout.jade index 8bc71a8..0b43f92 100644 --- a/views/layout.jade +++ b/views/layout.jade @@ -19,10 +19,28 @@ mixin cardNameImage(card) img.card-name(src='/images/default-card.png') mixin applyButton(card) - a.btn.btn-lg.btn-success(href=card.applyNowLink) + a.btn.btn.btn-lg.btn-success(href=card.applyNowLink) i.fa.fa-lock(aria-hidden="true") | APPLY NOW +mixin collapsibleMarketingCopy(card) + ul + each copyLine in card.mainMarketingCopy + li= copyLine + if card.extraMarketingCopy && card.extraMarketingCopy.length + ul.collapse + each copyLine in card.extraMarketingCopy + li= copyLine + a.show-hide(href='#') + | Show More + +mixin marketingCopy(card) + div.marketing-copy + if card.mainMarketingCopy + +collapsibleMarketingCopy(card) + else + span.missing-text 'No Info' + doctype html(lang='en') head meta(charset='utf-8') @@ -47,3 +65,20 @@ head block content block modals block scripts + script. + $(function () { + $('.marketing-copy a.show-hide').click(function (e) { + var $el = $(e.target), + copy = $el.siblings('.collapse') + e.preventDefault() + copy.collapse('toggle') + }) + + $('.marketing-copy .collapse') + .on('show.bs.collapse', function (e) { + $(e.target).siblings('a.show-hide').text('Show Less') + }) + .on('hide.bs.collapse', function (e) { + $(e.target).siblings('a.show-hide').text('Show More') + }) + }) diff --git a/views/offers.jade b/views/offers.jade index 5879079..bd22ea1 100644 --- a/views/offers.jade +++ b/views/offers.jade @@ -26,22 +26,14 @@ block content h2 We weren't able to find any offers div.cards.container - each product in products + each card in products div.card-info div.main-info div.details p.card-title= card.productDisplayName div.summary +cardNameImage(card) - ul.marketingCopy - if card.primaryBenefitDescription - li= card.primaryBenefitDescription - if card.purchaseAPRDescription - li= card.purchaseAPRDescription - if card.balanceTransferAPRDescription - li= card.balanceTransferAPRDescription - if card.annualMembershipFeeDescription - li= card.annualMembershipFeeDescription + +marketingCopy(card) div.apply +applyButton(card) p with CapitalOne® From de66de84d395118cf011cfa2926dd7b1d705fa19 Mon Sep 17 00:00:00 2001 From: rkorsak Date: Fri, 28 Oct 2016 18:17:23 -0400 Subject: [PATCH 060/147] Switch to just lorem ipsum copy for now --- mock_api/mockproducts.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mock_api/mockproducts.js b/mock_api/mockproducts.js index 4ac0ea3..c10a287 100644 --- a/mock_api/mockproducts.js +++ b/mock_api/mockproducts.js @@ -59,14 +59,14 @@ var randomizeProduct = function (product) { 'Earn bonus points on all purchases from Jake\'s Smoothie Hut', 'Use it to buy something nice for your mother. She deserves it after all you\'ve put her through' ], - randomMarketingCopy = _.map(_.range(15), function () { + randomMarketingCopy = _.map(_.range(25), function () { return loremIpsum({ - count: _.random(12, 50), + count: _.random(5, 50), units: 'words', format: 'plain' }) }), - marketingCopy = _.concat(customMarketingCopy, randomMarketingCopy) + marketingCopy = randomMarketingCopy var newValues = { productDisplayName: productDisplayName, From 0b1470996f7100f28dcdae7557de33193950ac0b Mon Sep 17 00:00:00 2001 From: rkorsak Date: Fri, 28 Oct 2016 18:17:45 -0400 Subject: [PATCH 061/147] Display API errors in index --- routes/index.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/routes/index.js b/routes/index.js index f9510c5..cb143bc 100644 --- a/routes/index.js +++ b/routes/index.js @@ -49,7 +49,9 @@ module.exports = function (options) { } client.products.getCards(requestedCardType.name, { limit: productCount }, function (err, data) { - cards = _.map(data.products, productViewModel) + if (err) { return next(err) } + + cards = _.map(_.get(data, 'products', []), productViewModel) res.render('index', { csrfToken: req.csrfToken(), title: 'Credit Offers Reference App', From 47e5455d10455cafdf105c5efd952c3c638ce9b4 Mon Sep 17 00:00:00 2001 From: rkorsak Date: Wed, 2 Nov 2016 15:19:47 -0400 Subject: [PATCH 062/147] Removed non-lorem-ipsum text --- mock_api/mockproducts.js | 22 ++-------------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/mock_api/mockproducts.js b/mock_api/mockproducts.js index c10a287..9fc2822 100644 --- a/mock_api/mockproducts.js +++ b/mock_api/mockproducts.js @@ -42,31 +42,13 @@ var randomizeProduct = function (product) { bonusMiles = format('%d0,000', _.random(1, 5)), rewardSpendingMin = format('%d,000', _.random(1, 3)), rewardMonthLimit = _.random(3, 10).toString(), - // Marketing copy - customMarketingCopy = [ - format('Enjoy a one-time bonus of %s miles once you spend $%s on purchases within %d months of approval, equal to $200 in travel', - bonusMiles, rewardSpendingMin, rewardMonthLimit), - format('Earn unlimited %d miles per dollar on every purchase, every day and pay no annual fee', milesPerDollar), - 'Fly any airline, stay at any hotel, anytime', - 'Complies with the standard expected size for a credit card, guaranteeing a snug fit in any wallet', - 'Impress your friends by pretending your new card is a tiny skateboard, and using your fingers as a tiny person to perform cool tricks', - 'Collect multiple cards and build your very own house of cards', - 'Explore the world! Accepted in over 400 countries', - 'Now you can own anything, anytime, anywhere!', - 'Get a head start on your bucket list', - 'Keep your credit going in the right direction', - 'Official credit card of the Milwaukee Corn Dogs', - 'Earn bonus points on all purchases from Jake\'s Smoothie Hut', - 'Use it to buy something nice for your mother. She deserves it after all you\'ve put her through' - ], - randomMarketingCopy = _.map(_.range(25), function () { + marketingCopy = _.map(_.range(25), function () { return loremIpsum({ count: _.random(5, 50), units: 'words', format: 'plain' }) - }), - marketingCopy = randomMarketingCopy + }) var newValues = { productDisplayName: productDisplayName, From bb76dc42eda3ec3e3b94e1d584c5b617434c8de3 Mon Sep 17 00:00:00 2001 From: rkorsak Date: Wed, 2 Nov 2016 15:20:17 -0400 Subject: [PATCH 063/147] Dropping the product display limit to 10 --- routes/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/routes/index.js b/routes/index.js index cb143bc..4eeffe6 100644 --- a/routes/index.js +++ b/routes/index.js @@ -38,7 +38,7 @@ module.exports = function (options) { ] // How many products to pull at a time - var productCount = 20 + var productCount = 10 /* GET home page. */ router.get('/', csrfProtection, function (req, res, next) { @@ -50,7 +50,7 @@ module.exports = function (options) { client.products.getCards(requestedCardType.name, { limit: productCount }, function (err, data) { if (err) { return next(err) } - + cards = _.map(_.get(data, 'products', []), productViewModel) res.render('index', { csrfToken: req.csrfToken(), From 0144a27b767efbfc5055c038934962ad24127272 Mon Sep 17 00:00:00 2001 From: rkorsak Date: Mon, 14 Nov 2016 15:40:26 -0500 Subject: [PATCH 064/147] Updated routes and mock API to use correct values for product types in URLs --- mock_api/routes/products.js | 17 ++++++++++++++++- routes/index.js | 4 ++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/mock_api/routes/products.js b/mock_api/routes/products.js index 2891f19..965e5bb 100644 --- a/mock_api/routes/products.js +++ b/mock_api/routes/products.js @@ -5,6 +5,12 @@ var mockproducts = require('../mockproducts') var debug = require('debug')('credit-offers_mock_api:products') var format = require('util').format +// Product types in the card response and API URL differ, so map them here +var productTypes = { + 'consumer': 'ConsumerCard', + 'business': 'BusinessCard' +} + var fakeProducts = mockproducts({ ConsumerCard: 100, BusinessCard: 100 @@ -55,7 +61,16 @@ makeResponse) router.get('/credit-offers/products/cards/:cardType', function (req, res, next) { console.info(req.body) - var cardType = req.params.cardType + var cardType = productTypes[req.params.cardType] + if (!cardType) { + res.status = 400 + res.json({ + code: null, + description: 'Unknown card type: ' + req.params.cardType + }) + return + } + res.locals.products = _(fakeProducts).filter({ productType: cardType }) next() }, diff --git a/routes/index.js b/routes/index.js index 4eeffe6..2dc27b0 100644 --- a/routes/index.js +++ b/routes/index.js @@ -28,11 +28,11 @@ module.exports = function (options) { // The supported card types var cardTypes = [ { - name: 'ConsumerCard', + name: 'consumer', display: 'Consumer Cards' }, { - name: 'BusinessCard', + name: 'business', display: 'Business Cards' } ] From 8f3024dfa0c7ffb980f2a54a8abda916bcdee2d7 Mon Sep 17 00:00:00 2001 From: rkorsak Date: Mon, 14 Nov 2016 15:40:42 -0500 Subject: [PATCH 065/147] Full response body logging --- creditoffers/client.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/creditoffers/client.js b/creditoffers/client.js index f4fe1f1..decb068 100644 --- a/creditoffers/client.js +++ b/creditoffers/client.js @@ -17,6 +17,7 @@ var request = require('request') var _ = require('lodash') var format = require('util').format var debug = require('debug')('credit-offers:api-client') +var util = require('util') // Default to a secure call to the API endpoint var defaultOptions = { @@ -84,7 +85,7 @@ function processResponse (err, response, body, callback) { return processResponseErrors(body, callback) } else if (response.statusCode >= 200) { // Pass the body contents back to the caller - debug('Received response', body) + debug('Received response', util.inspect(body, false, null)) return callback(null, body) } else { // Unknown status code From cdccabf63906a9666603e3e2ba211109b1a146f3 Mon Sep 17 00:00:00 2001 From: rkorsak Date: Mon, 14 Nov 2016 15:41:32 -0500 Subject: [PATCH 066/147] Explicitly pull only the required customer fields for prequal --- routes/offers.js | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/routes/offers.js b/routes/offers.js index fa4d814..fb2729e 100644 --- a/routes/offers.js +++ b/routes/offers.js @@ -32,7 +32,19 @@ module.exports = function (options) { router.post('/', csrfProtection, function (req, res, next) { // Build the customer info (moving address into its own object) // NOTE: In a production app, make sure to perform more in-depth validation of your inputs - var customerInfo = _.assign({}, req.body) + var customerProps = [ + 'firstName', + 'middleName', + 'lastName', + 'nameSuffix', + 'taxId', + 'dateOfBirth', + 'emailAddress', + 'annualIncome', + 'selfAssessedCreditRating', + 'bankAccountSummary', + 'requestedBenefit' + ] var addressProps = [ 'addressLine1', 'addressLine2', @@ -43,9 +55,8 @@ module.exports = function (options) { 'postalCode', 'addressType' ] - var address = _.pick(customerInfo, addressProps) - customerInfo = _.omit(customerInfo, addressProps) - customerInfo.address = address + var customerInfo = _.pick(req.body, customerProps) + customerInfo.address = _.pick(req.body, addressProps) client.prequalification.create(customerInfo, function (err, response) { if (err) { return next(err) } From eeaa53a1cf4c53b2c7ece3be2f1a13dc1e705ad2 Mon Sep 17 00:00:00 2001 From: rkorsak Date: Mon, 14 Nov 2016 17:37:27 -0500 Subject: [PATCH 067/147] Fixed incorrect setting of response status --- mock_api/routes/products.js | 2 +- package.json | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/mock_api/routes/products.js b/mock_api/routes/products.js index 965e5bb..aa351e9 100644 --- a/mock_api/routes/products.js +++ b/mock_api/routes/products.js @@ -63,7 +63,7 @@ router.get('/credit-offers/products/cards/:cardType', function (req, res, next) var cardType = productTypes[req.params.cardType] if (!cardType) { - res.status = 400 + res.status(400) res.json({ code: null, description: 'Unknown card type: ' + req.params.cardType diff --git a/package.json b/package.json index 85b1cd5..b547e78 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "debug": "~2.2.0", "ejs": "~2.3.3", "express": "~4.13.1", + "express-validator": "2.21.0", "font-awesome": "4.7.0", "helmet": "~1.1.0", "jade": "~1.11.0", From 53f2eb0628398aa348b3cfd80b3c8567b7b060e9 Mon Sep 17 00:00:00 2001 From: rkorsak Date: Mon, 14 Nov 2016 18:22:34 -0500 Subject: [PATCH 068/147] Basic server-side validation of prequal info --- app.js | 6 ++ routes/offers.js | 78 ++++++++++++------ validation/customValidators.js | 23 ++++++ validation/customerInfo.js | 144 +++++++++++++++++++++++++++++++++ validation/index.js | 22 +++++ validation/stateCodes.js | 80 ++++++++++++++++++ 6 files changed, 330 insertions(+), 23 deletions(-) create mode 100644 validation/customValidators.js create mode 100644 validation/customerInfo.js create mode 100644 validation/index.js create mode 100644 validation/stateCodes.js diff --git a/app.js b/app.js index 0db3f46..8e01f56 100644 --- a/app.js +++ b/app.js @@ -19,9 +19,12 @@ var favicon = require('serve-favicon') var logger = require('morgan') var cookieParser = require('cookie-parser') var bodyParser = require('body-parser') +var expressValidator = require('express-validator') var helmet = require('helmet') var csrf = require('csurf') +var validation = require('./validation') + var index = require('./routes/index') var offers = require('./routes/offers') @@ -37,6 +40,9 @@ app.use(favicon(path.join(__dirname, 'public', 'favicon.ico'))) app.use(logger('dev')) app.use(bodyParser.json()) app.use(bodyParser.urlencoded({ extended: false })) +app.use(expressValidator({ + customValidators: validation.customValidators +})) app.use(cookieParser()) app.use(express.static(path.join(__dirname, 'public'))) // Include the bootstrap package diff --git a/routes/offers.js b/routes/offers.js index fb2729e..83a0672 100644 --- a/routes/offers.js +++ b/routes/offers.js @@ -16,12 +16,14 @@ See the License for the specific language governing permissions and limitations /* Defines routes related to finding and displaying credit offers */ var express = require('express') +var util = require('util') var _ = require('lodash') var csrf = require('csurf') var CreditOffers = require('../creditoffers') var oauth = require('../oauth') var debug = require('debug')('credit-offers:offers') var productViewModel = require('../viewmodels').preQualProduct +var validation = require('../validation') module.exports = function (options) { var router = express.Router() @@ -29,9 +31,56 @@ module.exports = function (options) { var csrfProtection = csrf({ cookie: true }) // POST customer info to check for offers - router.post('/', csrfProtection, function (req, res, next) { + router.post('/', + csrfProtection, + function (req, res, next) { + // Strip out the CSRF token + delete req.body._csrf + + // Validate the request body + // NOTE: In a larger app, it would be worth pulling this logic out into + // more reusable middleware + req.checkBody(validation.models.customerInfo) + var errors = req.validationErrors(true) + if (errors) { + debug('Validation errors in request body!', util.inspect(errors, false, null)) + next(new Error('Bad form data')) + return + } + + // Strip out empty fields + req.body = _.omitBy(req.body, function (value, key) { return value == '' }) + + // Custom body sanitizing + req.sanitizeBody('annualIncome').toInt() + + next() + }, + function (req, res, next) { + var customerInfo = getCustomerInfo(req.body) + + client.prequalification.create(customerInfo, function (err, response) { + if (err) { return next(err) } + + var apiProducts = response.products || [] + var productViewModels = _(apiProducts) + .sortBy('priority') // Display in the priority order given by the API + .map(productViewModel) // Transform to a view model for easier display + .value() + + var viewModel = { + title: 'Credit Offers', + isPrequalified: response.isPrequalified, + prequalificationId: response.prequalificationId, + products: productViewModels + } + + res.render('offers', viewModel) + }) + }) + + function getCustomerInfo(body) { // Build the customer info (moving address into its own object) - // NOTE: In a production app, make sure to perform more in-depth validation of your inputs var customerProps = [ 'firstName', 'middleName', @@ -55,28 +104,11 @@ module.exports = function (options) { 'postalCode', 'addressType' ] - var customerInfo = _.pick(req.body, customerProps) - customerInfo.address = _.pick(req.body, addressProps) - - client.prequalification.create(customerInfo, function (err, response) { - if (err) { return next(err) } - - var apiProducts = response.products || [] - var productViewModels = _(apiProducts) - .sortBy('priority') // Display in the priority order given by the API - .map(productViewModel) // Transform to a view model for easier display - .value() - - var viewModel = { - title: 'Credit Offers', - isPrequalified: response.isPrequalified, - prequalificationId: response.prequalificationId, - products: productViewModels - } + var customerInfo = _.pick(body, customerProps) + customerInfo.address = _.pick(body, addressProps) - res.render('offers', viewModel) - }) - }) + return customerInfo + } // POST acknowledgement that prequal offers were displayed router.post('/acknowledge/:id', function (req, res, next) { diff --git a/validation/customValidators.js b/validation/customValidators.js new file mode 100644 index 0000000..cc5b7bf --- /dev/null +++ b/validation/customValidators.js @@ -0,0 +1,23 @@ +/* +Copyright 2016 Capital One Services, LLC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and limitations under the License. +*/ + +var stateCodes = require('./stateCodes') +var _ = require('lodash') + +module.exports = { + isUSState: function (value) { + return _.includes(stateCodes, value) + } +} diff --git a/validation/customerInfo.js b/validation/customerInfo.js new file mode 100644 index 0000000..afc1239 --- /dev/null +++ b/validation/customerInfo.js @@ -0,0 +1,144 @@ +/* +Copyright 2016 Capital One Services, LLC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and limitations under the License. +*/ + +/** @module Validation schema for incoming customer information **/ +module.exports = { + // Name rules + 'firstName': { + matches: { + options: [/^[A-Za-z '-]{1,35}$/] + }, + errorMessage: 'First Name is invalid' + }, + 'middleName': { + optional: { options: { checkFalsy: true } }, + matches: { + options: [/^[A-Za-z -]{0,1}$/] + }, + errorMessage: 'Middle Name is invalid' + }, + 'lastName': { + matches: { + options: [/^[A-Za-z '-]{2,35}$/] + }, + errorMessage: 'Last Name is invalid' + }, + 'nameSuffix': { + matches: { + options: [/^[A-Za-z. -]{0,9}$/] + }, + errorMessage: 'Name Suffix is invalid' + }, + + // Address rules + 'addressLine1': { + notEmpty: { errorMessage: 'Address Line 1 is required' }, + isLength: { + options: [{ min: 1, max: 60 }], + errorMessage: 'Address Line 1 cannot be longer than 60 characters' + }, + errorMessage: 'Address Line 1 is invalid' + }, + 'addressLine2': { + optional: { options: { checkFalsy: true } }, + isLength: { + options: [{ min: 0, max: 60 }], + errorMessage: 'Address Line 2 cannot be longer than 60 characters' + }, + errorMessage: 'Address Line 2 is invalid' + }, + 'addressLine3': { + optional: { options: { checkFalsy: true } }, + isLength: { + options: [{ min: 0, max: 60 }], + errorMessage: 'Address Line 3 cannot be longer than 60 characters' + }, + errorMessage: 'Address Line 3 is invalid' + }, + 'addressLine4': { + optional: { options: { checkFalsy: true } }, + isLength: { + options: [{ min: 0, max: 60 }], + errorMessage: 'Address Line 4 cannot be longer than 60 characters' + }, + errorMessage: 'Address Line 4 is invalid' + }, + 'city': { + notEmpty: { errorMessage: 'City is required' }, + isLength: { + options: [{ min: 0, max: 60 }], + errorMessage: 'City cannot be longer than 35 characters' + }, + errorMessage: 'City is invalid' + }, + 'stateCode': { + notEmpty: { errorMessage: 'State is required' }, + isUSState: true, + errorMessage: 'State is invalid' + }, + 'postalCode': { + notEmpty: { errorMessage: 'Postal Code is required' }, + isNumeric: true, + isLength: { options: [{ min: 5, max: 5 }] }, + errorMessage: 'Postal Code is invalid' + }, + 'addressType': { + optional: { options: { checkFalsy: true } }, + isIn: { options: [ 'Home', 'Business' ] }, + errorMessage: 'Address Type is invalid' + }, + + // Other values + 'taxId': { + notEmpty: { errorMessage: 'Tax ID is required' }, + matches: { + options: [/^\d{4}|\d{9}$/], + errorMessage: 'Either the full nine digits or last four digits of the Tax ID are required' + }, + errorMessage: 'Tax ID is invalid' + }, + 'dateOfBirth': { + optional: { options: { checkFalsy: true } }, + matches: { + options: [/^\d{4}-\d{2}-\d{2}$/], + }, + errorMessage: 'Date of Birth is invalid' + }, + 'emailAddress': { + optional: { options: { checkFalsy: true } }, + isEmail: true, + errorMessage: 'Email is invalid' + }, + 'annualIncome': { + optional: { options: { checkFalsy: true } }, + isNumeric: true, + errorMessage: 'Annual Income is invalid' + }, + 'selfAssessedCreditRating': { + optional: { options: { checkFalsy: true } }, + isIn: { options: [['Excellent', 'Average', 'Rebuilding']] }, + errorMessage: 'Self-Assessed Credit Rating is invalid' + }, + 'bankAccountSummary': { + optional: { options: { checkFalsy: true } }, + isIn: { options: [['CheckingAndSavings', 'CheckingOnly', 'SavingsOnly', 'Neither']] }, + errorMessage: 'Bank Account Summary is invalid' + }, + 'requestedBenefit': { + optional: { options: { checkFalsy: true } }, + isIn: { options: [['LowInterest', 'TravelRewards', 'CashBack', 'NotSure']] }, + errorMessage: 'Requested Benefit is invalid' + } +} diff --git a/validation/index.js b/validation/index.js new file mode 100644 index 0000000..3bd6191 --- /dev/null +++ b/validation/index.js @@ -0,0 +1,22 @@ +/* +Copyright 2016 Capital One Services, LLC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and limitations under the License. +*/ + +module.exports = { + models: { + customerInfo: require('./customerInfo') + }, + customValidators: require('./customValidators'), + stateCodes: require('./stateCodes') +} diff --git a/validation/stateCodes.js b/validation/stateCodes.js new file mode 100644 index 0000000..ca4bfb5 --- /dev/null +++ b/validation/stateCodes.js @@ -0,0 +1,80 @@ +/* +Copyright 2016 Capital One Services, LLC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and limitations under the License. +*/ + +module.exports = [ + 'AL', + 'AK', + 'AS', + 'AZ', + 'AR', + 'CA', + 'CO', + 'CT', + 'DE', + 'DC', + 'FM', + 'FL', + 'GA', + 'GU', + 'HI', + 'ID', + 'IL', + 'IN', + 'IA', + 'KS', + 'KY', + 'LA', + 'ME', + 'MH', + 'MD', + 'MA', + 'MI', + 'MN', + 'MS', + 'MO', + 'MT', + 'NE', + 'NV', + 'NH', + 'NJ', + 'NM', + 'NY', + 'NC', + 'ND', + 'MP', + 'OH', + 'OK', + 'OR', + 'PW', + 'PA', + 'PR', + 'RI', + 'SC', + 'SD', + 'TN', + 'TX', + 'UT', + 'VT', + 'VI', + 'VA', + 'WA', + 'WV', + 'WI', + 'WY', + 'DC', + 'AA', + 'AE', + 'AP' +] From 6d24dbabea4ef7ab16600e3838f1cd6387a6e8b4 Mon Sep 17 00:00:00 2001 From: rkorsak Date: Tue, 15 Nov 2016 11:22:04 -0500 Subject: [PATCH 069/147] Added HTML sanitization package for displaying html from the API --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index b547e78..ecdb777 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "lodash": "~4.1.0", "morgan": "~1.6.1", "request": "~2.69.0", + "sanitize-html": "1.13.0", "serve-favicon": "~2.3.0" }, "engines": { From af59fd009f2871bf643f1c0a80de708eae4e9ba0 Mon Sep 17 00:00:00 2001 From: rkorsak Date: Tue, 15 Nov 2016 11:22:27 -0500 Subject: [PATCH 070/147] Added HTML entity decoding package --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index ecdb777..af6c8e0 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "express-validator": "2.21.0", "font-awesome": "4.7.0", "helmet": "~1.1.0", + "html-entities": "1.2.0", "jade": "~1.11.0", "lodash": "~4.1.0", "morgan": "~1.6.1", From b32db3e9fb817cc103766b47fe7aa7c1d6eb5603 Mon Sep 17 00:00:00 2001 From: rkorsak Date: Tue, 15 Nov 2016 12:17:38 -0500 Subject: [PATCH 071/147] Display sanitized HTML from the API responses --- helpers/index.js | 18 ++++++++++++++++ helpers/sanitize.js | 41 ++++++++++++++++++++++++++++++++++++ viewmodels/preQualProduct.js | 11 +++++----- viewmodels/product.js | 5 +++-- views/index.jade | 13 ++++++++---- views/layout.jade | 4 ++-- views/offers.jade | 5 +++-- 7 files changed, 82 insertions(+), 15 deletions(-) create mode 100644 helpers/index.js create mode 100644 helpers/sanitize.js diff --git a/helpers/index.js b/helpers/index.js new file mode 100644 index 0000000..a9a4f41 --- /dev/null +++ b/helpers/index.js @@ -0,0 +1,18 @@ +/* +Copyright 2016 Capital One Services, LLC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and limitations under the License. +*/ + +module.exports = { + sanitize: require('./sanitize') +} diff --git a/helpers/sanitize.js b/helpers/sanitize.js new file mode 100644 index 0000000..613de78 --- /dev/null +++ b/helpers/sanitize.js @@ -0,0 +1,41 @@ +/* +Copyright 2016 Capital One Services, LLC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and limitations under the License. +*/ + +var sanitizeHtml = require('sanitize-html') +var Entities = require('html-entities').XmlEntities; + +// For decoding xml entities in responses from the Capital One API +entities = new Entities(); + +// HTML tags to be kept in responses from the Capital One API +var allowedTags = [ + 'sup' // The registered trademark symbol may be returned in a tag for proper display +] + +/** + * The Capital One API may return values to be displayed as HTML. + * This function decodes and sanitizes those values + */ +exports.sanitizeHtmlForDisplay = function sanitizeHtmlForDisplay (value) { + if (!value) return value + + // Decode entities first + var decodedValue = entities.decode(value) + + // Then sanitize + return sanitizeHtml(decodedValue, { + allowedTags: allowedTags + }) +} diff --git a/viewmodels/preQualProduct.js b/viewmodels/preQualProduct.js index 950e1fa..5f5b7a3 100644 --- a/viewmodels/preQualProduct.js +++ b/viewmodels/preQualProduct.js @@ -16,24 +16,25 @@ See the License for the specific language governing permissions and limitations /** @module Defines a consistent model for displaying pre-qualification products, which contain less info than normal products **/ var _ = require('lodash') +var sanitize = require('../helpers').sanitize.sanitizeHtmlForDisplay module.exports = function preQualProduct (apiProduct) { var viewModel = _.pick(apiProduct, [ 'productId', - 'productDisplayName', 'tier', 'terms', 'additionalInformationUrl' ]) + viewModel.productDisplayName = sanitize(apiProduct.productDisplayName || apiProduct.productName || '???') viewModel.images = { cardName: _.find(apiProduct.images, { imageType: 'CardName' }) } // Normalize to the keys used by the products API - viewModel.primaryBenefitDescription = _.get(apiProduct, 'terms.primaryBenefit') - viewModel.purchaseAPRDescription = _.get(apiProduct, 'terms.purchaseAprTerms') - viewModel.balanceTransferAPRDescription = _.get(apiProduct, 'terms.balanceTransferTerms') - viewModel.annualMembershipFeeDescription = _.get(apiProduct, 'terms.annualMembershipFeeTerms') + viewModel.primaryBenefitDescription = sanitize(_.get(apiProduct, 'terms.primaryBenefit')) + viewModel.purchaseAPRDescription = sanitize(_.get(apiProduct, 'terms.purchaseAprTerms')) + viewModel.balanceTransferAPRDescription = sanitize(_.get(apiProduct, 'terms.balanceTransferTerms')) + viewModel.annualMembershipFeeDescription = sanitize(_.get(apiProduct, 'terms.annualMembershipFeeTerms')) viewModel.applyNowLink = apiProduct.applicationUrl var marketingCopy = _.compact([ diff --git a/viewmodels/product.js b/viewmodels/product.js index 83cb689..ce416de 100644 --- a/viewmodels/product.js +++ b/viewmodels/product.js @@ -16,11 +16,11 @@ See the License for the specific language governing permissions and limitations /** @module Defines a consistent model for displaying products from the API **/ var _ = require('lodash') +var sanitize = require('../helpers').sanitize.sanitizeHtmlForDisplay module.exports = function product (apiProduct) { var viewModel = _.pick(apiProduct, [ 'productId', - 'productDisplayName', 'activeFrom', 'activeTo', 'publishedDate', @@ -48,13 +48,14 @@ module.exports = function product (apiProduct) { 'promotionalDescriptions' ]) + viewModel.productDisplayName = sanitize(apiProduct.productDisplayName || '???') viewModel.images = { cardName: _.find(apiProduct.images, { imageType: 'CardName' }) } viewModel.additionalInformationUrl = _.get(apiProduct, 'links.self.href') - var marketingCopy = apiProduct.marketingCopy || [] + var marketingCopy = _.map(apiProduct.marketingCopy || [], sanitize) viewModel.mainMarketingCopy = _.take(marketingCopy, 2) viewModel.extraMarketingCopy = _.drop(marketingCopy, 2) diff --git a/views/index.jade b/views/index.jade index fc25d74..1ad9052 100644 --- a/views/index.jade +++ b/views/index.jade @@ -23,8 +23,12 @@ block content div.container div.row div.col-md-8 - h2 CapitalOne® Credit Cards - p These are some of the cards on offer from CapitalOne®. + h2 CapitalOne + sup ® + | Credit Cards + p These are some of the cards on offer from CapitalOne + sup ® + | . div.filters.col-md-4 div.pull-right div.btn-group(role="group", aria-label="Card Types") @@ -35,13 +39,14 @@ block content div.card-info div.main-info div.details - p.card-title= card.productDisplayName + p.card-title !{card.productDisplayName} div.summary +cardNameImage(card) +marketingCopy(card) div.apply +applyButton(card) - p with CapitalOne® + p with CapitalOne + sup ® div.additional-info block modals diff --git a/views/layout.jade b/views/layout.jade index 0b43f92..f59bc95 100644 --- a/views/layout.jade +++ b/views/layout.jade @@ -26,11 +26,11 @@ mixin applyButton(card) mixin collapsibleMarketingCopy(card) ul each copyLine in card.mainMarketingCopy - li= copyLine + li !{copyLine} if card.extraMarketingCopy && card.extraMarketingCopy.length ul.collapse each copyLine in card.extraMarketingCopy - li= copyLine + li !{copyLine} a.show-hide(href='#') | Show More diff --git a/views/offers.jade b/views/offers.jade index bd22ea1..d38924a 100644 --- a/views/offers.jade +++ b/views/offers.jade @@ -30,13 +30,14 @@ block content div.card-info div.main-info div.details - p.card-title= card.productDisplayName + p.card-title !{card.productDisplayName} div.summary +cardNameImage(card) +marketingCopy(card) div.apply +applyButton(card) - p with CapitalOne® + p with CapitalOne + sup ® block scripts script. From 36318934292f2453c15e11703a3fd9da26c87562 Mon Sep 17 00:00:00 2001 From: rkorsak Date: Tue, 15 Nov 2016 12:18:39 -0500 Subject: [PATCH 072/147] Refactored API client for more accurate logging and less repetition --- creditoffers/client.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/creditoffers/client.js b/creditoffers/client.js index decb068..4e1fc35 100644 --- a/creditoffers/client.js +++ b/creditoffers/client.js @@ -55,8 +55,12 @@ ApiClient.prototype.sendRequest = function _sendRequest (reqOptions, callback) { // Populate the above request defaults if not passed in _.defaults(reqOptions, defaultRequestSettings) - - debug('Sending request', reqOptions) + var send = function() { + debug('Sending request', reqOptions) + request(reqOptions, function (err, response, body) { + processResponse(err, response, body, callback) + }) + } if (reqOptions.useOAuth) { @@ -64,15 +68,11 @@ ApiClient.prototype.sendRequest = function _sendRequest (reqOptions, callback) { this.oauth.withToken(function (err, token) { if (err) { return callback(err) } reqOptions.auth = { bearer: token.access_token } - request(reqOptions, function (err, response, body) { - processResponse(err, response, body, callback) - }) + send() }) } else { - request(reqOptions, function (err, response, body) { - processResponse(err, response, body, callback) - }) + send() } } From 485c2ed7b07e97ff1bb1f3a3b3fd18a49d9bbc87 Mon Sep 17 00:00:00 2001 From: rkorsak Date: Tue, 15 Nov 2016 12:48:15 -0500 Subject: [PATCH 073/147] Fixed productName in prequal view model --- viewmodels/preQualProduct.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/viewmodels/preQualProduct.js b/viewmodels/preQualProduct.js index 5f5b7a3..e9f2de5 100644 --- a/viewmodels/preQualProduct.js +++ b/viewmodels/preQualProduct.js @@ -26,7 +26,7 @@ module.exports = function preQualProduct (apiProduct) { 'additionalInformationUrl' ]) - viewModel.productDisplayName = sanitize(apiProduct.productDisplayName || apiProduct.productName || '???') + viewModel.productDisplayName = sanitize(apiProduct.productName || '???') viewModel.images = { cardName: _.find(apiProduct.images, { imageType: 'CardName' }) } From 1e4a58abc81acf1e924bdb5fc8cf2e01b2b3e15e Mon Sep 17 00:00:00 2001 From: rkorsak Date: Tue, 15 Nov 2016 13:05:45 -0500 Subject: [PATCH 074/147] Placeholder card image --- public/images/default-card.png | Bin 0 -> 160888 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 public/images/default-card.png diff --git a/public/images/default-card.png b/public/images/default-card.png new file mode 100644 index 0000000000000000000000000000000000000000..463156d7c78e194a90eab64e5281f00da2a691d6 GIT binary patch literal 160888 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 HC9nSfcr~bE literal 0 HcmV?d00001 From 504d7f150993560b07d348773f19b115f9e3be05 Mon Sep 17 00:00:00 2001 From: rkorsak Date: Tue, 15 Nov 2016 13:13:29 -0500 Subject: [PATCH 075/147] Display product name as a link if a URL is available --- views/index.jade | 3 ++- views/layout.jade | 7 +++++++ views/offers.jade | 3 ++- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/views/index.jade b/views/index.jade index 1ad9052..0faf96c 100644 --- a/views/index.jade +++ b/views/index.jade @@ -39,7 +39,8 @@ block content div.card-info div.main-info div.details - p.card-title !{card.productDisplayName} + p.card-title + +cardNameLink(card) div.summary +cardNameImage(card) +marketingCopy(card) diff --git a/views/layout.jade b/views/layout.jade index f59bc95..ab11587 100644 --- a/views/layout.jade +++ b/views/layout.jade @@ -12,6 +12,13 @@ //- See the License for the specific language governing permissions and limitations under the License. //- Common Card mixins +mixin cardNameLink(card) + if card.additionalInformationUrl + span + a(href=card.additionalInformationUrl) !{card.productDisplayName} + else + span !{card.productDisplayName} + mixin cardNameImage(card) if card.images && card.images.cardName img.card-name(src=card.images.cardName.url, alt=card.images.cardName.alternateText) diff --git a/views/offers.jade b/views/offers.jade index d38924a..ea45a54 100644 --- a/views/offers.jade +++ b/views/offers.jade @@ -30,7 +30,8 @@ block content div.card-info div.main-info div.details - p.card-title !{card.productDisplayName} + p.card-title + +cardNameLink(card) div.summary +cardNameImage(card) +marketingCopy(card) From f8b1a44ccc02685b51e9105c5986166a16990050 Mon Sep 17 00:00:00 2001 From: rkorsak Date: Tue, 15 Nov 2016 13:20:05 -0500 Subject: [PATCH 076/147] Tiny jade syntax fix --- views/index.jade | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/views/index.jade b/views/index.jade index 0faf96c..6602c6b 100644 --- a/views/index.jade +++ b/views/index.jade @@ -58,8 +58,7 @@ block modals input(type="hidden" name="_csrf" value="#{csrfToken}") div.modal-header button.close(type='button', data-dismiss='modal', aria-label='close') - span(aria-hidden='true') - × + span(aria-hidden='true') × h4.modal-title Tell us a little about yourself div.modal-body include ./includes/customer-form From d84ed71cc65b53a92baeec8378baaa953cd16003 Mon Sep 17 00:00:00 2001 From: rkorsak Date: Tue, 15 Nov 2016 14:12:06 -0500 Subject: [PATCH 077/147] Display server-side validation errors in UI --- routes/offers.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/routes/offers.js b/routes/offers.js index 83a0672..181539b 100644 --- a/routes/offers.js +++ b/routes/offers.js @@ -44,7 +44,10 @@ module.exports = function (options) { var errors = req.validationErrors(true) if (errors) { debug('Validation errors in request body!', util.inspect(errors, false, null)) - next(new Error('Bad form data')) + var failSummary = _(errors).map(function (error) { + return error.msg + }).value().join('; ') + next(new Error('Validation failed: ' + failSummary)) return } From 360178f0c8bef1a6b00d98975feeb490d6a86f33 Mon Sep 17 00:00:00 2001 From: rkorsak Date: Tue, 15 Nov 2016 14:12:18 -0500 Subject: [PATCH 078/147] Fix regex for tax ID --- validation/customerInfo.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/validation/customerInfo.js b/validation/customerInfo.js index afc1239..386541b 100644 --- a/validation/customerInfo.js +++ b/validation/customerInfo.js @@ -104,7 +104,7 @@ module.exports = { 'taxId': { notEmpty: { errorMessage: 'Tax ID is required' }, matches: { - options: [/^\d{4}|\d{9}$/], + options: [/^(\d{4}|\d{9})$/], errorMessage: 'Either the full nine digits or last four digits of the Tax ID are required' }, errorMessage: 'Tax ID is invalid' From 2f6994b0e86d1a0aa896a895a1907fcf635dc616 Mon Sep 17 00:00:00 2001 From: rkorsak Date: Tue, 15 Nov 2016 14:19:51 -0500 Subject: [PATCH 079/147] Refactor routing to use a single shared API client --- app.js | 8 ++++++-- routes/index.js | 5 +---- routes/offers.js | 5 +---- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/app.js b/app.js index 8e01f56..a15ef45 100644 --- a/app.js +++ b/app.js @@ -25,6 +25,8 @@ var csrf = require('csurf') var validation = require('./validation') +var CreditOffers = require('./creditoffers') +var oauth = require('./oauth') var index = require('./routes/index') var offers = require('./routes/offers') @@ -54,8 +56,10 @@ app.use('/fonts', express.static(path.join(__dirname, 'node_modules/font-awesome app.use(helmet()) -app.use('/', index(config.creditOffers)) -app.use('/offers', offers(config.creditOffers)) +// Initialize the routing +var client = new CreditOffers(config.creditOffers.client, oauth(config.creditOffers.oauth)) +app.use('/', index(client)) +app.use('/offers', offers(client)) // catch 404 and forward to error handler app.use(function (req, res, next) { diff --git a/routes/index.js b/routes/index.js index 2dc27b0..ac6d2f9 100644 --- a/routes/index.js +++ b/routes/index.js @@ -14,14 +14,11 @@ See the License for the specific language governing permissions and limitations */ var express = require('express') -var CreditOffers = require('../creditoffers') -var oauth = require('../oauth') var csrf = require('csurf') var _ = require('lodash') var productViewModel = require('../viewmodels').product -module.exports = function (options) { - var client = new CreditOffers(options.client, oauth(options.oauth)) +module.exports = function (client) { var csrfProtection = csrf({ cookie: true }) var router = express.Router() diff --git a/routes/offers.js b/routes/offers.js index 181539b..35a3baf 100644 --- a/routes/offers.js +++ b/routes/offers.js @@ -19,15 +19,12 @@ var express = require('express') var util = require('util') var _ = require('lodash') var csrf = require('csurf') -var CreditOffers = require('../creditoffers') -var oauth = require('../oauth') var debug = require('debug')('credit-offers:offers') var productViewModel = require('../viewmodels').preQualProduct var validation = require('../validation') -module.exports = function (options) { +module.exports = function (client) { var router = express.Router() - var client = new CreditOffers(options.client, oauth(options.oauth)) var csrfProtection = csrf({ cookie: true }) // POST customer info to check for offers From a6eed2163a829867eaeffff884f21f3510a38ac4 Mon Sep 17 00:00:00 2001 From: rkorsak Date: Wed, 16 Nov 2016 13:34:07 -0500 Subject: [PATCH 080/147] Updated README file --- README.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index b8aa226..a06e52e 100644 --- a/README.md +++ b/README.md @@ -25,16 +25,21 @@ From the project root: ### Try it out -Navigate to http://localhost:3000. You will see a simple page with a button to launch a customer info modal. Enter some fake customer data (at least the minimum required fields) and submit the form. +Navigate to http://localhost:3000. This will retrieve a list of customer card products from the API and display simple information about each. From here, you can try a few simple things: -This will submit a request to the Credit Offers endpoint and redirect the user to a page displaying the offers. + * Toggle the card type to 'Business' to request and display a list of business card products from the API + * Click on the 'Find Pre-Qualified Offers' button to launch a simple customer information form and test out the pre-qualification API behavior. + +#### A note about errors + +For demonstration purposes, API and server-side validation errors are displayed in an error page in the UI. A full production-ready application should have more robust error handling, and keep a smooth user experience. ### Viewing more details To get a deeper look at the messages being passed, start the app with the following command `DEBUG=credit-offers:* NODE_DEBUG=request npm start`. This will activate detailed debug logging to the console, showing the details of the request to the API and the response received. ## Best Practices -This application makes use of the [helmet](https://www.npmjs.com/package/helmet) library for safer http headers, and the [csurf](https://www.npmjs.com/package/csurf) library to avoid cross-site request forgery attacks. However, when developing and hosting a real world application, make sure to be aware of the [security](http://expressjs.com/en/advanced/best-practice-security.html) and [performance](http://expressjs.com/en/advanced/best-practice-performance.html) best practices for the Express framework. In particular, hosting with TLS is strongly recommended and free certificates can be acquired at https://letsencrypt.org/. +This application makes use of the [helmet](https://www.npmjs.com/package/helmet) library for safer http headers, the [csurf](https://www.npmjs.com/package/csurf) library to avoid cross-site request forgery attacks, the [express-validator](https://www.npmjs.com/package/express-validator) library to validate customer info on the server side, and the [sanitize-html](https://www.npmjs.com/package/sanitize-html) library to safely sanitize values from the API before displaying them as HTML to the user. However, when developing and hosting a real world application, make sure to be aware of the [security](http://expressjs.com/en/advanced/best-practice-security.html) and [performance](http://expressjs.com/en/advanced/best-practice-performance.html) best practices for the Express framework. In particular, hosting with TLS is strongly recommended and free certificates can be acquired at https://letsencrypt.org/. ## Architecture This is a [Node.js](https://nodejs.org) 4.x and higher app built with [Express](http://expressjs.com/) 4.13.1. Because of the simple nature, there is no session management or data persistence. @@ -57,4 +62,3 @@ This project adheres to the [Open Source Code of Conduct][code-of-conduct]. By p ### Contribution Guidelines We encourage any contributions that align with the intent of this project and add more functionality or languages that other developers can make use of. To contribute to the project, please submit a PR for our review. Before contributing any source code, familiarize yourself with the Apache License 2.0 (license.md), which controls the licensing for this project. - From b4559838fc623c7b9d592851b4f4b65f69444320 Mon Sep 17 00:00:00 2001 From: rkorsak Date: Wed, 16 Nov 2016 13:43:10 -0500 Subject: [PATCH 081/147] Add Apache License text to www file --- bin/www | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/bin/www b/bin/www index ea78b12..e0d23a8 100644 --- a/bin/www +++ b/bin/www @@ -1,4 +1,18 @@ #!/usr/bin/env node +/* +Copyright 2016 Capital One Services, LLC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and limitations under the License. +*/ /** * Module dependencies. From a2cb352ecdda679d6a136a0877367a4d19477010 Mon Sep 17 00:00:00 2001 From: rkorsak Date: Wed, 16 Nov 2016 14:03:51 -0500 Subject: [PATCH 082/147] Regenerated license notices as markdown --- credit-offers_notices.md | 236 +++++++++++++++ credit-offers_notices.txt | 610 -------------------------------------- 2 files changed, 236 insertions(+), 610 deletions(-) create mode 100644 credit-offers_notices.md delete mode 100644 credit-offers_notices.txt diff --git a/credit-offers_notices.md b/credit-offers_notices.md new file mode 100644 index 0000000..f8bc057 --- /dev/null +++ b/credit-offers_notices.md @@ -0,0 +1,236 @@ +# Dependency Licenses +*Generated using [license-checker](https://github.com/davglass/license-checker), with some manual adjustments* + + * [accepts@1.2.13](https://github.com/jshttp/accepts) - MIT + * [acorn-globals@1.0.9](https://github.com/ForbesLindesay/acorn-globals) - MIT + * [acorn@1.2.2](https://github.com/marijnh/acorn) - MIT + * [acorn@2.7.0](https://github.com/ternjs/acorn) - MIT + * [align-text@0.1.4](https://github.com/jonschlinkert/align-text) - MIT + * [amdefine@1.0.0](https://github.com/jrburke/amdefine) - BSD-3-Clause AND MIT + * [ansi-regex@2.0.0](https://github.com/sindresorhus/ansi-regex) - MIT + * [ansi-styles@2.2.1](https://github.com/chalk/ansi-styles) - MIT + * [array-flatten@1.1.1](https://github.com/blakeembrey/array-flatten) - MIT + * [asap@1.0.0](undefined) - MIT + * [asn1@0.2.3](https://github.com/mcavage/node-asn1) - MIT + * [assert-plus@0.2.0](https://github.com/mcavage/node-assert-plus) - MIT + * [assert-plus@1.0.0](https://github.com/mcavage/node-assert-plus) - MIT + * [async@0.2.10](https://github.com/caolan/async) - MIT + * [async@2.1.2](https://github.com/caolan/async) - MIT + * [aws-sign2@0.6.0](https://github.com/mikeal/aws-sign) - Apache-2.0 + * [aws4@1.5.0](https://github.com/mhart/aws4) - MIT + * [base64-url@1.2.2](https://github.com/joaquimserafim/base64-url) - ISC + * [basic-auth@1.0.4](https://github.com/jshttp/basic-auth) - MIT + * [bcrypt-pbkdf@1.0.0](undefined) - BSD-4-Clause + * [bl@1.0.3](https://github.com/rvagg/bl) - MIT + * [bluebird@3.4.6](https://github.com/petkaantonov/bluebird) - MIT + * [body-parser@1.13.3](https://github.com/expressjs/body-parser) - MIT + * [boom@2.10.1](https://github.com/hapijs/boom) - BSD-3-Clause + * [bootstrap@3.3.7](https://github.com/twbs/bootstrap) - MIT + * [bytes@2.1.0](https://github.com/visionmedia/bytes.js) - MIT + * [bytes@2.4.0](https://github.com/visionmedia/bytes.js) - MIT + * [camelcase@1.2.1](https://github.com/sindresorhus/camelcase) - MIT + * [camelize@1.0.0](https://github.com/substack/camelize) - MIT + * [caseless@0.11.0](https://github.com/mikeal/caseless) - Apache-2.0 + * [center-align@0.1.3](https://github.com/jonschlinkert/center-align) - MIT + * [chalk@1.1.3](https://github.com/chalk/chalk) - MIT + * [character-parser@1.2.1](https://github.com/ForbesLindesay/character-parser) - MIT + * [clean-css@3.4.20](https://github.com/jakubpawlowicz/clean-css) - MIT + * [cliui@2.1.0](https://github.com/bcoe/cliui) - ISC + * [combined-stream@1.0.5](https://github.com/felixge/node-combined-stream) - MIT + * [commander@2.6.0](https://github.com/tj/commander.js) - MIT + * [commander@2.8.1](https://github.com/tj/commander.js) - MIT + * [commander@2.9.0](https://github.com/tj/commander.js) - MIT + * [connect@3.4.0](https://github.com/senchalabs/connect) - MIT + * [constantinople@3.0.2](https://github.com/ForbesLindesay/constantinople) - MIT + * [content-disposition@0.5.1](https://github.com/jshttp/content-disposition) - MIT + * [content-security-policy-builder@1.0.0](https://github.com/helmetjs/content-security-policy-builder) - MIT + * [content-type@1.0.2](https://github.com/jshttp/content-type) - MIT + * [cookie-parser@1.3.5](https://github.com/expressjs/cookie-parser) - MIT + * [cookie-signature@1.0.6](https://github.com/visionmedia/node-cookie-signature) - MIT + * [cookie@0.1.3](https://github.com/jshttp/cookie) - MIT + * [cookie@0.1.5](https://github.com/jshttp/cookie) - MIT + * [core-util-is@1.0.2](https://github.com/isaacs/core-util-is) - MIT + * [credit-offers@2.0.0](https://github.com/capitalone/CreditOffers-API-reference-app) - Apache-2.0 + * [cryptiles@2.0.5](https://github.com/hapijs/cryptiles) - BSD-3-Clause + * [csrf@3.0.3](https://github.com/pillarjs/csrf) - MIT + * [css-parse@1.0.4](undefined) - MIT + * [css-stringify@1.0.5](undefined) - MIT + * [css@1.0.8](undefined) - MIT + * [csurf@1.8.3](https://github.com/expressjs/csurf) - MIT + * [dashdash@1.14.0](https://github.com/trentm/node-dashdash) - MIT + * [dashify@0.2.2](https://github.com/jonschlinkert/dashify) - MIT + * [debug@2.2.0](https://github.com/visionmedia/debug) - MIT + * [decamelize@1.2.0](https://github.com/sindresorhus/decamelize) - MIT + * [delayed-stream@1.0.0](https://github.com/felixge/node-delayed-stream) - MIT + * [depd@1.0.1](https://github.com/dougwilson/nodejs-depd) - MIT + * [depd@1.1.0](https://github.com/dougwilson/nodejs-depd) - MIT + * [destroy@1.0.4](https://github.com/stream-utils/destroy) - MIT + * [dns-prefetch-control@0.1.0](https://github.com/helmetjs/dns-prefetch-control) - MIT + * [dom-serializer@0.1.0](https://github.com/cheeriojs/dom-renderer) - MIT + * [domelementtype@1.1.3](https://github.com/FB55/domelementtype) - BSD* + * [domelementtype@1.3.0](https://github.com/FB55/domelementtype) - BSD* + * [domhandler@2.3.0](https://github.com/fb55/DomHandler) - BSD* + * [domutils@1.5.1](https://github.com/FB55/domutils) - BSD* + * [dont-sniff-mimetype@1.0.0](https://github.com/helmetjs/dont-sniff-mimetype) - MIT + * [ecc-jsbn@0.1.1](https://github.com/quartzjer/ecc-jsbn) - MIT + * [ee-first@1.1.1](https://github.com/jonathanong/ee-first) - MIT + * [ejs@2.3.4](https://github.com/mde/ejs) - Apache-2.0 + * [entities@1.1.1](https://github.com/fb55/node-entities) - BSD-like + * [escape-html@1.0.2](https://github.com/component/escape-html) - MIT + * [escape-html@1.0.3](https://github.com/component/escape-html) - MIT + * [escape-string-regexp@1.0.5](https://github.com/sindresorhus/escape-string-regexp) - MIT + * [etag@1.7.0](https://github.com/jshttp/etag) - MIT + * [express-validator@2.21.0](https://github.com/ctavan/express-validator) - MIT + * [express@4.13.4](https://github.com/expressjs/express) - MIT + * [extend@3.0.0](https://github.com/justmoon/node-extend) - MIT + * [extsprintf@1.0.2](https://github.com/davepacheco/node-extsprintf) - MIT* + * [finalhandler@0.4.0](https://github.com/pillarjs/finalhandler) - MIT + * [finalhandler@0.4.1](https://github.com/pillarjs/finalhandler) - MIT + * [font-awesome@4.7.0](https://github.com/FortAwesome/Font-Awesome) - (OFL-1.1 AND MIT) + * [forever-agent@0.6.1](https://github.com/mikeal/forever-agent) - Apache-2.0 + * [form-data@1.0.1](https://github.com/form-data/form-data) - MIT + * [forwarded@0.1.0](https://github.com/jshttp/forwarded) - MIT + * [frameguard@1.0.0](https://github.com/helmetjs/frameguard) - MIT + * [fresh@0.3.0](https://github.com/jshttp/fresh) - MIT + * [generate-function@2.0.0](https://github.com/mafintosh/generate-function) - MIT + * [generate-object-property@1.2.0](https://github.com/mafintosh/generate-object-property) - MIT + * [getpass@0.1.6](https://github.com/arekinath/node-getpass) - MIT + * [graceful-readlink@1.0.1](https://github.com/zhiyelee/graceful-readlink) - MIT + * [har-validator@2.0.6](https://github.com/ahmadnassri/har-validator) - ISC + * [has-ansi@2.0.0](https://github.com/sindresorhus/has-ansi) - MIT + * [hawk@3.1.3](https://github.com/hueniverse/hawk) - BSD-3-Clause + * [helmet-csp@1.0.3](https://github.com/helmetjs/csp) - MIT + * [helmet@1.1.0](https://github.com/helmetjs/helmet) - MIT + * [hide-powered-by@1.0.0](https://github.com/helmetjs/hide-powered-by) - MIT + * [hoek@2.16.3](https://github.com/hapijs/hoek) - BSD-3-Clause + * [hpkp@1.0.0](https://github.com/helmetjs/hpkp) - MIT + * [hsts@1.0.0](https://github.com/helmetjs/hsts) - MIT + * [html-entities@1.2.0](https://github.com/mdevils/node-html-entities) - MIT + * [htmlparser2@3.9.2](https://github.com/fb55/htmlparser2) - MIT + * [http-errors@1.3.1](https://github.com/jshttp/http-errors) - MIT + * [http-signature@1.1.1](https://github.com/joyent/node-http-signature) - MIT + * [iconv-lite@0.4.11](https://github.com/ashtuchkin/iconv-lite) - MIT + * [iconv-lite@0.4.13](https://github.com/ashtuchkin/iconv-lite) - MIT + * [ienoopen@1.0.0](https://github.com/helmetjs/ienoopen) - MIT + * [inherits@2.0.3](https://github.com/isaacs/inherits) - ISC + * [ipaddr.js@1.0.5](https://github.com/whitequark/ipaddr.js) - MIT + * [is-buffer@1.1.4](https://github.com/feross/is-buffer) - MIT + * [is-my-json-valid@2.15.0](https://github.com/mafintosh/is-my-json-valid) - MIT + * [is-promise@1.0.1](https://github.com/then/is-promise) - MIT + * [is-promise@2.1.0](https://github.com/then/is-promise) - MIT + * [is-property@1.0.2](https://github.com/mikolalysenko/is-property) - MIT + * [is-typedarray@1.0.0](https://github.com/hughsk/is-typedarray) - MIT + * [isarray@1.0.0](https://github.com/juliangruber/isarray) - MIT + * [isstream@0.1.2](https://github.com/rvagg/isstream) - MIT + * [jade@1.11.0](https://github.com/jadejs/jade) - MIT + * [jodid25519@1.0.2](https://github.com/meganz/jodid25519) - MIT + * [jsbn@0.1.0](https://github.com/andyperlitch/jsbn) - BSD + * [json-schema@0.2.3](https://github.com/kriszyp/json-schema) - AFLv2.1,BSD + * [json-stringify-safe@5.0.1](https://github.com/isaacs/json-stringify-safe) - ISC + * [jsonpointer@4.0.0](https://github.com/janl/node-jsonpointer) - MIT + * [jsprim@1.3.1](https://github.com/davepacheco/node-jsprim) - MIT + * [jstransformer@0.0.2](https://github.com/jstransformers/jstransformer) - MIT + * [kind-of@3.0.4](https://github.com/jonschlinkert/kind-of) - MIT + * [lazy-cache@1.0.4](https://github.com/jonschlinkert/lazy-cache) - MIT + * [lodash._baseassign@3.2.0](https://github.com/lodash/lodash) - MIT + * [lodash._basecallback@3.3.1](https://github.com/lodash/lodash) - MIT + * [lodash._basecopy@3.0.1](https://github.com/lodash/lodash) - MIT + * [lodash._baseeach@3.0.4](https://github.com/lodash/lodash) - MIT + * [lodash._baseisequal@3.0.7](https://github.com/lodash/lodash) - MIT + * [lodash._basereduce@3.0.2](https://github.com/lodash/lodash) - MIT + * [lodash._bindcallback@3.0.1](https://github.com/lodash/lodash) - MIT + * [lodash._createassigner@3.1.1](https://github.com/lodash/lodash) - MIT + * [lodash._getnative@3.9.1](https://github.com/lodash/lodash) - MIT + * [lodash._isiterateecall@3.0.9](https://github.com/lodash/lodash) - MIT + * [lodash.assign@3.2.0](https://github.com/lodash/lodash) - MIT + * [lodash.isarguments@3.1.0](https://github.com/lodash/lodash) - MIT + * [lodash.isarray@3.0.4](https://github.com/lodash/lodash) - MIT + * [lodash.isfunction@3.0.6](https://github.com/lodash/lodash) - MIT + * [lodash.isstring@3.0.1](https://github.com/lodash/lodash) - MIT + * [lodash.istypedarray@3.0.6](https://github.com/lodash/lodash) - MIT + * [lodash.keys@3.1.2](https://github.com/lodash/lodash) - MIT + * [lodash.pairs@3.0.1](https://github.com/lodash/lodash) - MIT + * [lodash.reduce@3.1.2](https://github.com/lodash/lodash) - MIT + * [lodash.restparam@3.6.1](https://github.com/lodash/lodash) - MIT + * [lodash.some@3.2.3](https://github.com/lodash/lodash) - MIT + * [lodash@4.1.0](https://github.com/lodash/lodash) - MIT + * [lodash@4.16.4](https://github.com/lodash/lodash) - MIT + * [lodash@4.16.6](https://github.com/lodash/lodash) - MIT + * [longest@1.0.1](https://github.com/jonschlinkert/longest) - MIT + * [media-typer@0.3.0](https://github.com/jshttp/media-typer) - MIT + * [merge-descriptors@1.0.1](https://github.com/component/merge-descriptors) - MIT + * [methods@1.1.2](https://github.com/jshttp/methods) - MIT + * [mime-db@1.24.0](https://github.com/jshttp/mime-db) - MIT + * [mime-types@2.1.12](https://github.com/jshttp/mime-types) - MIT + * [mime@1.3.4](https://github.com/broofa/node-mime) - MIT + * [minimist@0.0.8](https://github.com/substack/minimist) - MIT + * [mkdirp@0.5.1](https://github.com/substack/node-mkdirp) - MIT + * [morgan@1.6.1](https://github.com/expressjs/morgan) - MIT + * [ms@0.7.1](https://github.com/guille/ms.js) - MIT* + * [negotiator@0.5.3](https://github.com/jshttp/negotiator) - MIT + * [nocache@1.0.0](https://github.com/helmetjs/nocache) - MIT + * [node-uuid@1.4.7](https://github.com/broofa/node-uuid) - MIT + * [oauth-sign@0.8.2](https://github.com/mikeal/oauth-sign) - Apache-2.0 + * [on-finished@2.3.0](https://github.com/jshttp/on-finished) - MIT + * [on-headers@1.0.1](https://github.com/jshttp/on-headers) - MIT + * [optimist@0.3.7](https://github.com/substack/node-optimist) - MIT/X11 + * [parseurl@1.3.1](https://github.com/pillarjs/parseurl) - MIT + * [path-to-regexp@0.1.7](https://github.com/component/path-to-regexp) - MIT + * [pinkie-promise@2.0.1](https://github.com/floatdrop/pinkie-promise) - MIT + * [pinkie@2.0.4](https://github.com/floatdrop/pinkie) - MIT + * [platform@1.3.0](https://github.com/bestiejs/platform.js) - MIT + * [process-nextick-args@1.0.7](https://github.com/calvinmetcalf/process-nextick-args) - MIT + * [promise@2.0.0](https://github.com/then/promise) - MIT + * [promise@6.1.0](https://github.com/then/promise) - MIT + * [proxy-addr@1.0.10](https://github.com/jshttp/proxy-addr) - MIT + * [qs@4.0.0](https://github.com/hapijs/qs) - BSD-3-Clause + * [qs@6.0.2](https://github.com/ljharb/qs) - BSD-3-Clause + * [random-bytes@1.0.0](https://github.com/crypto-utils/random-bytes) - MIT + * [range-parser@1.0.3](https://github.com/jshttp/range-parser) - MIT + * [raw-body@2.1.7](https://github.com/stream-utils/raw-body) - MIT + * [readable-stream@2.0.6](https://github.com/nodejs/readable-stream) - MIT + * [regexp-quote@0.0.0](https://github.com/dbrock/node-regexp-quote) - MIT + * [repeat-string@1.5.4](https://github.com/jonschlinkert/repeat-string) - MIT + * [request@2.69.0](https://github.com/request/request) - Apache-2.0 + * [right-align@0.1.3](https://github.com/jonschlinkert/right-align) - MIT + * [rndm@1.2.0](https://github.com/crypto-utils/rndm) - MIT + * [sanitize-html@1.13.0](https://github.com/punkave/sanitize-html) - MIT + * [send@0.13.1](https://github.com/pillarjs/send) - MIT + * [send@0.13.2](https://github.com/pillarjs/send) - MIT + * [serve-favicon@2.3.0](https://github.com/expressjs/serve-favicon) - MIT + * [serve-static@1.10.3](https://github.com/expressjs/serve-static) - MIT + * [sntp@1.0.9](https://github.com/hueniverse/sntp) - BSD + * [source-map@0.1.43](https://github.com/mozilla/source-map) - BSD + * [source-map@0.4.4](https://github.com/mozilla/source-map) - BSD-3-Clause + * [source-map@0.5.6](https://github.com/mozilla/source-map) - BSD-3-Clause + * [sshpk@1.10.1](https://github.com/arekinath/node-sshpk) - MIT + * [statuses@1.2.1](https://github.com/jshttp/statuses) - MIT + * [statuses@1.3.0](https://github.com/jshttp/statuses) - MIT + * [string_decoder@0.10.31](https://github.com/rvagg/string_decoder) - MIT + * [stringstream@0.0.5](https://github.com/mhart/StringStream) - MIT + * [strip-ansi@3.0.1](https://github.com/chalk/strip-ansi) - MIT + * [supports-color@2.0.0](https://github.com/chalk/supports-color) - MIT + * [tough-cookie@2.2.2](https://github.com/SalesforceEng/tough-cookie) - BSD-3-Clause + * [transformers@2.1.0](https://github.com/ForbesLindesay/transformers) - MIT + * [tsscmp@1.0.5](https://github.com/suryagh/tsscmp) - MIT + * [tunnel-agent@0.4.3](https://github.com/mikeal/tunnel-agent) - Apache-2.0 + * [tweetnacl@0.14.3](https://github.com/dchest/tweetnacl-js) - Public Domain + * [type-is@1.6.13](https://github.com/jshttp/type-is) - MIT + * [uglify-js@2.2.5](https://github.com/mishoo/UglifyJS2) - BSD + * [uglify-js@2.7.3](https://github.com/mishoo/UglifyJS2) - BSD-2-Clause + * [uglify-to-browserify@1.0.2](https://github.com/ForbesLindesay/uglify-to-browserify) - MIT + * [uid-safe@2.1.1](https://github.com/crypto-utils/uid-safe) - MIT + * [unpipe@1.0.0](https://github.com/stream-utils/unpipe) - MIT + * [util-deprecate@1.0.2](https://github.com/TooTallNate/util-deprecate) - MIT + * [utils-merge@1.0.0](https://github.com/jaredhanson/utils-merge) - MIT + * [validator@5.7.0](https://github.com/chriso/validator.js) - MIT + * [vary@1.0.1](https://github.com/jshttp/vary) - MIT + * [verror@1.3.6](https://github.com/davepacheco/node-verror) - MIT* + * [void-elements@2.0.1](https://github.com/hemanth/void-elements) - MIT + * [window-size@0.1.0](https://github.com/jonschlinkert/window-size) - MIT + * [with@4.0.3](https://github.com/ForbesLindesay/with) - MIT + * [wordwrap@0.0.2](https://github.com/substack/node-wordwrap) - MIT/X11 + * [wordwrap@0.0.3](https://github.com/substack/node-wordwrap) - MIT + * [x-xss-protection@1.0.0](https://github.com/helmetjs/x-xss-protection) - MIT + * [xtend@4.0.1](https://github.com/Raynos/xtend) - MIT + * [yargs@3.10.0](https://github.com/bcoe/yargs) - MIT diff --git a/credit-offers_notices.txt b/credit-offers_notices.txt deleted file mode 100644 index ee95a3e..0000000 --- a/credit-offers_notices.txt +++ /dev/null @@ -1,610 +0,0 @@ -accepts@1.2.13 - licenses: MIT - repository: https://github.com/jshttp/accepts -acorn-globals@1.0.9 - licenses: MIT - repository: https://github.com/ForbesLindesay/acorn-globals -acorn@1.2.2 - licenses: MIT - repository: https://github.com/marijnh/acorn -acorn@2.7.0 - licenses: MIT - repository: https://github.com/ternjs/acorn -align-text@0.1.4 - licenses: MIT - repository: https://github.com/jonschlinkert/align-text -amdefine@1.0.0 - licenses: BSD-3-Clause AND MIT - repository: https://github.com/jrburke/amdefine -ansi-regex@2.0.0 - licenses: MIT - repository: https://github.com/sindresorhus/ansi-regex -ansi-styles@2.2.0 - licenses: MIT - repository: https://github.com/chalk/ansi-styles -array-flatten@1.1.1 - licenses: MIT - repository: https://github.com/blakeembrey/array-flatten -asap@1.0.0 - licenses: MIT - repository: https://github.com/kriskowal/asap -asn1@0.2.3 - licenses: MIT - repository: https://github.com/mcavage/node-asn1 -assert-plus@0.2.0 - licenses: MIT - repository: https://github.com/mcavage/node-assert-plus -assert-plus@1.0.0 - licenses: MIT - repository: https://github.com/mcavage/node-assert-plus -async@0.2.10 - licenses: MIT - repository: https://github.com/caolan/async -async@1.5.2 - licenses: MIT - repository: https://github.com/caolan/async -aws-sign2@0.6.0 - licenses: Apache-2.0 - repository: https://github.com/mikeal/aws-sign -aws4@1.3.1 - licenses: MIT - repository: https://github.com/mhart/aws4 -base64-url@1.2.1 - licenses: ISC - repository: https://github.com/joaquimserafim/base64-url -basic-auth@1.0.3 - licenses: MIT - repository: https://github.com/jshttp/basic-auth -bl@1.0.3 - licenses: MIT - repository: https://github.com/rvagg/bl -body-parser@1.13.3 - licenses: MIT - repository: https://github.com/expressjs/body-parser -boom@2.10.1 - licenses: BSD-3-Clause - repository: https://github.com/hapijs/boom -bytes@2.1.0 - licenses: MIT - repository: https://github.com/visionmedia/bytes.js -bytes@2.2.0 - licenses: MIT - repository: https://github.com/visionmedia/bytes.js -camelcase@1.2.1 - licenses: MIT - repository: https://github.com/sindresorhus/camelcase -camelize@1.0.0 - licenses: MIT - repository: https://github.com/substack/camelize -caseless@0.11.0 - licenses: Apache-2.0 - repository: https://github.com/mikeal/caseless -center-align@0.1.3 - licenses: MIT - repository: https://github.com/jonschlinkert/center-align -chalk@1.1.1 - licenses: MIT - repository: https://github.com/chalk/chalk -character-parser@1.2.1 - licenses: MIT - repository: https://github.com/ForbesLindesay/character-parser -clean-css@3.4.10 - licenses: MIT - repository: https://github.com/jakubpawlowicz/clean-css -cliui@2.1.0 - licenses: ISC - repository: https://github.com/bcoe/cliui -color-convert@1.0.0 - licenses: MIT - repository: https://github.com/qix-/color-convert -combined-stream@1.0.5 - licenses: MIT - repository: https://github.com/felixge/node-combined-stream -commander@2.6.0 - licenses: MIT - repository: https://github.com/tj/commander.js -commander@2.8.1 - licenses: MIT - repository: https://github.com/tj/commander.js -commander@2.9.0 - licenses: MIT - repository: https://github.com/tj/commander.js -connect@3.4.1 - licenses: MIT - repository: https://github.com/senchalabs/connect -constantinople@3.0.2 - licenses: MIT - repository: https://github.com/ForbesLindesay/constantinople -content-disposition@0.5.1 - licenses: MIT - repository: https://github.com/jshttp/content-disposition -content-security-policy-builder@1.0.0 - licenses: MIT - repository: https://github.com/helmetjs/content-security-policy-builder -content-type@1.0.1 - licenses: MIT - repository: https://github.com/jshttp/content-type -cookie-parser@1.3.5 - licenses: MIT - repository: https://github.com/expressjs/cookie-parser -cookie-signature@1.0.6 - licenses: MIT - repository: https://github.com/visionmedia/node-cookie-signature -cookie@0.1.3 - licenses: MIT - repository: https://github.com/jshttp/cookie -cookie@0.1.5 - licenses: MIT - repository: https://github.com/jshttp/cookie -core-util-is@1.0.2 - licenses: MIT - repository: https://github.com/isaacs/core-util-is -credit-offers@0.0.1 - licenses: Apache-2.0 -cryptiles@2.0.5 - licenses: BSD-3-Clause - repository: https://github.com/hapijs/cryptiles -csrf@3.0.1 - licenses: MIT - repository: https://github.com/pillarjs/csrf -css-parse@1.0.4 - licenses: MIT - repository: https://github.com/reworkcss/css-parse -css-stringify@1.0.5 - licenses: MIT - repository: https://github.com/reworkcss/css-stringify -css@1.0.8 - licenses: MIT - repository: https://github.com/reworkcss/css -csurf@1.8.3 - licenses: MIT - repository: https://github.com/expressjs/csurf -dashdash@1.13.0 - licenses: MIT - repository: https://github.com/trentm/node-dashdash -dashify@0.2.1 - licenses: MIT - repository: https://github.com/jonschlinkert/dashify -debug@2.2.0 - licenses: MIT - repository: https://github.com/visionmedia/debug -decamelize@1.1.2 - licenses: MIT - repository: https://github.com/sindresorhus/decamelize -delayed-stream@1.0.0 - licenses: MIT - repository: https://github.com/felixge/node-delayed-stream -depd@1.0.1 - licenses: MIT - repository: https://github.com/dougwilson/nodejs-depd -depd@1.1.0 - licenses: MIT - repository: https://github.com/dougwilson/nodejs-depd -destroy@1.0.4 - licenses: MIT - repository: https://github.com/stream-utils/destroy -dns-prefetch-control@0.1.0 - licenses: MIT - repository: https://github.com/helmetjs/dns-prefetch-control -dont-sniff-mimetype@1.0.0 - licenses: MIT - repository: https://github.com/helmetjs/dont-sniff-mimetype -ecc-jsbn@0.1.1 - licenses: MIT - repository: https://github.com/quartzjer/ecc-jsbn -ee-first@1.1.1 - licenses: MIT - repository: https://github.com/jonathanong/ee-first -ejs@2.3.4 - licenses: Apache-2.0 - repository: https://github.com/mde/ejs -escape-html@1.0.3 - licenses: MIT - repository: https://github.com/component/escape-html -escape-string-regexp@1.0.5 - licenses: MIT - repository: https://github.com/sindresorhus/escape-string-regexp -etag@1.7.0 - licenses: MIT - repository: https://github.com/jshttp/etag -express@4.13.4 - licenses: MIT - repository: https://github.com/expressjs/express -extend@3.0.0 - licenses: MIT - repository: https://github.com/justmoon/node-extend -extsprintf@1.0.2 - licenses: MIT* - repository: https://github.com/davepacheco/node-extsprintf -finalhandler@0.4.1 - licenses: MIT - repository: https://github.com/pillarjs/finalhandler -forever-agent@0.6.1 - licenses: Apache-2.0 - repository: https://github.com/mikeal/forever-agent -form-data@1.0.0-rc3 - licenses: MIT - repository: https://github.com/form-data/form-data -forwarded@0.1.0 - licenses: MIT - repository: https://github.com/jshttp/forwarded -frameguard@1.1.0 - licenses: MIT - repository: https://github.com/helmetjs/frameguard -fresh@0.3.0 - licenses: MIT - repository: https://github.com/jshttp/fresh -generate-function@2.0.0 - licenses: MIT - repository: https://github.com/mafintosh/generate-function -generate-object-property@1.2.0 - licenses: MIT - repository: https://github.com/mafintosh/generate-object-property -graceful-readlink@1.0.1 - licenses: MIT - repository: https://github.com/zhiyelee/graceful-readlink -har-validator@2.0.6 - licenses: ISC - repository: https://github.com/ahmadnassri/har-validator -has-ansi@2.0.0 - licenses: MIT - repository: https://github.com/sindresorhus/has-ansi -hawk@3.1.3 - licenses: BSD-3-Clause - repository: https://github.com/hueniverse/hawk -helmet-csp@1.1.0 - licenses: MIT - repository: https://github.com/helmetjs/csp -helmet@1.2.0 - licenses: MIT - repository: https://github.com/helmetjs/helmet -hide-powered-by@1.0.0 - licenses: MIT - repository: https://github.com/helmetjs/hide-powered-by -hoek@2.16.3 - licenses: BSD-3-Clause - repository: https://github.com/hapijs/hoek -hpkp@1.0.0 - licenses: MIT - repository: https://github.com/helmetjs/hpkp -hsts@1.0.0 - licenses: MIT - repository: https://github.com/helmetjs/hsts -http-errors@1.3.1 - licenses: MIT - repository: https://github.com/jshttp/http-errors -http-signature@1.1.1 - licenses: MIT - repository: https://github.com/joyent/node-http-signature -iconv-lite@0.4.11 - licenses: MIT - repository: https://github.com/ashtuchkin/iconv-lite -iconv-lite@0.4.13 - licenses: MIT - repository: https://github.com/ashtuchkin/iconv-lite -ienoopen@1.0.0 - licenses: MIT - repository: https://github.com/helmetjs/ienoopen -inherits@2.0.1 - licenses: ISC - repository: https://github.com/isaacs/inherits -ipaddr.js@1.0.5 - licenses: MIT - repository: https://github.com/whitequark/ipaddr.js -is-buffer@1.1.2 - licenses: MIT - repository: https://github.com/feross/is-buffer -is-my-json-valid@2.13.1 - licenses: MIT - repository: https://github.com/mafintosh/is-my-json-valid -is-promise@1.0.1 - licenses: MIT - repository: https://github.com/then/is-promise -is-promise@2.1.0 - licenses: MIT - repository: https://github.com/then/is-promise -is-property@1.0.2 - licenses: MIT - repository: https://github.com/mikolalysenko/is-property -is-typedarray@1.0.0 - licenses: MIT - repository: https://github.com/hughsk/is-typedarray -isarray@0.0.1 - licenses: MIT - repository: https://github.com/juliangruber/isarray -isstream@0.1.2 - licenses: MIT - repository: https://github.com/rvagg/isstream -jade@1.11.0 - licenses: MIT - repository: https://github.com/jadejs/jade -jodid25519@1.0.2 - licenses: MIT - repository: https://github.com/meganz/jodid25519 -jsbn@0.1.0 - licenses: BSD - repository: https://github.com/andyperlitch/jsbn -json-schema@0.2.2 - licenses - - AFLv2.1 - - BSD - repository: https://github.com/kriszyp/json-schema -json-stringify-safe@5.0.1 - licenses: ISC - repository: https://github.com/isaacs/json-stringify-safe -jsonpointer@2.0.0 - licenses: MIT - repository: https://github.com/janl/node-jsonpointer -jsprim@1.2.2 - licenses: MIT - repository: https://github.com/davepacheco/node-jsprim -jstransformer@0.0.2 - licenses: MIT - repository: https://github.com/jstransformers/jstransformer -kind-of@3.0.2 - licenses: MIT - repository: https://github.com/jonschlinkert/kind-of -lazy-cache@1.0.3 - licenses: MIT - repository: https://github.com/jonschlinkert/lazy-cache -lodash._baseeach@4.1.0 - licenses: MIT - repository: https://github.com/lodash/lodash -lodash._baseiteratee@4.5.1 - licenses: MIT - repository: https://github.com/lodash/lodash -lodash._basereduce@3.0.2 - licenses: MIT - repository: https://github.com/lodash/lodash -lodash.assign@4.0.4 - licenses: MIT - repository: https://github.com/lodash/lodash -lodash.isfunction@3.0.8 - licenses: MIT - repository: https://github.com/lodash/lodash -lodash.isstring@4.0.1 - licenses: MIT - repository: https://github.com/lodash/lodash -lodash.keys@4.0.4 - licenses: MIT - repository: https://github.com/lodash/lodash -lodash.reduce@4.2.0 - licenses: MIT - repository: https://github.com/lodash/lodash -lodash.rest@4.0.1 - licenses: MIT - repository: https://github.com/lodash/lodash -lodash.some@4.2.0 - licenses: MIT - repository: https://github.com/lodash/lodash -lodash@4.5.1 - licenses: MIT - repository: https://github.com/lodash/lodash -longest@1.0.1 - licenses: MIT - repository: https://github.com/jonschlinkert/longest -lru-cache@4.0.0 - licenses: ISC - repository: https://github.com/isaacs/node-lru-cache -media-typer@0.3.0 - licenses: MIT - repository: https://github.com/jshttp/media-typer -merge-descriptors@1.0.1 - licenses: MIT - repository: https://github.com/component/merge-descriptors -methods@1.1.2 - licenses: MIT - repository: https://github.com/jshttp/methods -mime-db@1.22.0 - licenses: MIT - repository: https://github.com/jshttp/mime-db -mime-types@2.1.10 - licenses: MIT - repository: https://github.com/jshttp/mime-types -mime@1.3.4 - licenses: MIT - repository: https://github.com/broofa/node-mime -minimist@0.0.8 - licenses: MIT - repository: https://github.com/substack/minimist -mkdirp@0.5.1 - licenses: MIT - repository: https://github.com/substack/node-mkdirp -morgan@1.6.1 - licenses: MIT - repository: https://github.com/expressjs/morgan -ms@0.7.1 - licenses: MIT* - repository: https://github.com/guille/ms.js -negotiator@0.5.3 - licenses: MIT - repository: https://github.com/jshttp/negotiator -nocache@1.0.0 - licenses: MIT - repository: https://github.com/helmetjs/nocache -node-uuid@1.4.7 - licenses: MIT - repository: https://github.com/broofa/node-uuid -oauth-sign@0.8.1 - licenses: Apache-2.0 - repository: https://github.com/mikeal/oauth-sign -on-finished@2.3.0 - licenses: MIT - repository: https://github.com/jshttp/on-finished -on-headers@1.0.1 - licenses: MIT - repository: https://github.com/jshttp/on-headers -optimist@0.3.7 - licenses: MIT/X11 - repository: https://github.com/substack/node-optimist -parseurl@1.3.1 - licenses: MIT - repository: https://github.com/pillarjs/parseurl -path-to-regexp@0.1.7 - licenses: MIT - repository: https://github.com/component/path-to-regexp -pinkie-promise@2.0.0 - licenses: MIT - repository: https://github.com/floatdrop/pinkie-promise -pinkie@2.0.4 - licenses: MIT - repository: https://github.com/floatdrop/pinkie -platform@1.3.1 - licenses: MIT - repository: https://github.com/bestiejs/platform.js -process-nextick-args@1.0.6 - licenses: MIT - repository: https://github.com/calvinmetcalf/process-nextick-args -promise@2.0.0 - licenses: MIT - repository: https://github.com/then/promise -promise@6.1.0 - licenses: MIT - repository: https://github.com/then/promise -proxy-addr@1.0.10 - licenses: MIT - repository: https://github.com/jshttp/proxy-addr -pseudomap@1.0.2 - licenses: ISC - repository: https://github.com/isaacs/pseudomap -qs@4.0.0 - licenses: BSD-3-Clause - repository: https://github.com/hapijs/qs -qs@6.0.2 - licenses: BSD-3-Clause - repository: https://github.com/ljharb/qs -random-bytes@1.0.0 - licenses: MIT - repository: https://github.com/crypto-utils/random-bytes -range-parser@1.0.3 - licenses: MIT - repository: https://github.com/jshttp/range-parser -raw-body@2.1.5 - licenses: MIT - repository: https://github.com/stream-utils/raw-body -readable-stream@2.0.5 - licenses: MIT - repository: https://github.com/nodejs/readable-stream -repeat-string@1.5.4 - licenses: MIT - repository: https://github.com/jonschlinkert/repeat-string -request@2.69.0 - licenses: Apache-2.0 - repository: https://github.com/request/request -right-align@0.1.3 - licenses: MIT - repository: https://github.com/jonschlinkert/right-align -rndm@1.2.0 - licenses: MIT - repository: https://github.com/crypto-utils/rndm -scmp@1.0.0 - licenses: BSD - repository: https://github.com/freewil/scmp -send@0.13.1 - licenses: MIT - repository: https://github.com/pillarjs/send -serve-favicon@2.3.0 - licenses: MIT - repository: https://github.com/expressjs/serve-favicon -serve-static@1.10.2 - licenses: MIT - repository: https://github.com/expressjs/serve-static -sntp@1.0.9 - licenses: BSD - repository: https://github.com/hueniverse/sntp -source-map@0.1.43 - licenses: BSD - repository: https://github.com/mozilla/source-map -source-map@0.4.4 - licenses: BSD-3-Clause - repository: https://github.com/mozilla/source-map -source-map@0.5.3 - licenses: BSD-3-Clause - repository: https://github.com/mozilla/source-map -sshpk@1.7.4 - licenses: MIT - repository: https://github.com/arekinath/node-sshpk -statuses@1.2.1 - licenses: MIT - repository: https://github.com/jshttp/statuses -string_decoder@0.10.31 - licenses: MIT - repository: https://github.com/rvagg/string_decoder -stringstream@0.0.5 - licenses: MIT - repository: https://github.com/mhart/StringStream -strip-ansi@3.0.1 - licenses: MIT - repository: https://github.com/chalk/strip-ansi -supports-color@2.0.0 - licenses: MIT - repository: https://github.com/chalk/supports-color -tough-cookie@2.2.1 - licenses: BSD-3-Clause - repository: https://github.com/SalesforceEng/tough-cookie -transformers@2.1.0 - licenses: MIT - repository: https://github.com/ForbesLindesay/transformers -tunnel-agent@0.4.2 - licenses: Apache-2.0 - repository: https://github.com/mikeal/tunnel-agent -tweetnacl@0.14.1 - licenses: Public Domain - repository: https://github.com/dchest/tweetnacl-js -type-is@1.6.12 - licenses: MIT - repository: https://github.com/jshttp/type-is -uglify-js@2.2.5 - licenses: BSD - repository: https://github.com/mishoo/UglifyJS2 -uglify-js@2.6.2 - licenses: BSD-2-Clause - repository: https://github.com/mishoo/UglifyJS2 -uglify-to-browserify@1.0.2 - licenses: MIT - repository: https://github.com/ForbesLindesay/uglify-to-browserify -uid-safe@2.1.0 - licenses: MIT - repository: https://github.com/crypto-utils/uid-safe -unpipe@1.0.0 - licenses: MIT - repository: https://github.com/stream-utils/unpipe -util-deprecate@1.0.2 - licenses: MIT - repository: https://github.com/TooTallNate/util-deprecate -utils-merge@1.0.0 - licenses: MIT - repository: https://github.com/jaredhanson/utils-merge -vary@1.0.1 - licenses: MIT - repository: https://github.com/jshttp/vary -verror@1.3.6 - licenses: MIT* - repository: https://github.com/davepacheco/node-verror -void-elements@2.0.1 - licenses: MIT - repository: https://github.com/hemanth/void-elements -window-size@0.1.0 - licenses: MIT - repository: https://github.com/jonschlinkert/window-size -with@4.0.3 - licenses: MIT - repository: https://github.com/ForbesLindesay/with -wordwrap@0.0.2 - licenses: MIT/X11 - repository: https://github.com/substack/node-wordwrap -wordwrap@0.0.3 - licenses: MIT - repository: https://github.com/substack/node-wordwrap -x-xss-protection@1.0.0 - licenses: MIT - repository: https://github.com/helmetjs/x-xss-protection -xtend@4.0.1 - licenses: MIT - repository: https://github.com/Raynos/xtend -yallist@2.0.0 - licenses: ISC - repository: https://github.com/isaacs/yallist -yargs@3.10.0 - licenses: MIT - repository: https://github.com/bcoe/yargs From 0b8e9c544244cf96c8dc1dc116d87672a59dfe84 Mon Sep 17 00:00:00 2001 From: rkorsak Date: Wed, 16 Nov 2016 14:14:14 -0500 Subject: [PATCH 083/147] More README updates --- README.md | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a06e52e..24676eb 100644 --- a/README.md +++ b/README.md @@ -3,9 +3,22 @@ Credit Offers is a card acquisition service that provides prequalified credit offer listings based on end customer provided information. Affiliates are able to provide these offers directly without need of a web redirect. If a prequalified offer is not available, a default offer is returned by us. ## Software Requirements Including version -This is version 1.0 of the Credit Offers API Reference Application Code. For software requirements, see Build/Install Instructions below. +This is version 2.0 of the Credit Offers API Reference Application Code. For software requirements, see Build/Install Instructions below. -This reference app illustrates the use of the Credit Offers API to collect customer information and retrieve a list of targeted product offers for display. If you encounter any issues using this reference code, please submit them in the form of GitHub issues. +This reference app illustrates the use of the Credit Offers API to + +* Retrieve and display card products (Business or Consumer) using the `/credit-offers/products/cards/{cardType}` endpoint +* Collect customer information and retrieve a list of targeted product offers for display using the `/credit-offers/prequalifications` endpoint +* Send acknowledgement to Capital One that the targeted product offers have been displayed using the `/credit-offers/prequalifications/{prequalificationId}` endpoint + +Some additional API features that are **not** directly illustrated by this app include: + +* Using the `limit` and `offset` parameters to retrieve multiple pages of products +* Using the `/credit-offers/products` endpoint to retrieve summaries of *all* products +* Using the `/credit-offers/products/cards` endpoint to retrieve summaries of all card products +* Using the `/credit-offers/products/cards/{cardType}/{productId}` endpoint to retrieve information about a single specific card product + +If you encounter any issues using this reference code, please submit them in the form of GitHub issues. ## Build/Install Instructions ### Dependencies @@ -25,7 +38,7 @@ From the project root: ### Try it out -Navigate to http://localhost:3000. This will retrieve a list of customer card products from the API and display simple information about each. From here, you can try a few simple things: +Navigate to http://localhost:3000. This will retrieve a list of Consumer card products from the API and display simple information about each. From here, you can try a few simple things: * Toggle the card type to 'Business' to request and display a list of business card products from the API * Click on the 'Find Pre-Qualified Offers' button to launch a simple customer information form and test out the pre-qualification API behavior. From e6ebbd1231cc708299394edf455005354650a4e7 Mon Sep 17 00:00:00 2001 From: rkorsak Date: Wed, 16 Nov 2016 14:14:33 -0500 Subject: [PATCH 084/147] Added client code for /credit-offers/products --- creditoffers/products.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/creditoffers/products.js b/creditoffers/products.js index 2a54e50..8e13ac6 100644 --- a/creditoffers/products.js +++ b/creditoffers/products.js @@ -38,6 +38,22 @@ module.exports = Products Products.prototype.getAll = function getAll (pagingOptions, callback) { var query = _.pick(pagingOptions, ['limit', 'offset']) + this.client.sendRequest({ + url: '/credit-offers/products', + useOAuth: true, + method: 'GET', + qs: query + }, callback) +} + +/** + * Retrieve summary information on all card products + * @param {object} pagingOptions Optionally control the number of results and starting offset + * in the result set + */ +Products.prototype.getAllCards = function getAllCards (pagingOptions, callback) { + var query = _.pick(pagingOptions, ['limit', 'offset']) + this.client.sendRequest({ url: '/credit-offers/products/cards', useOAuth: true, From cff9ba87367fc33c1360cbf3c7638eaffa81d18f Mon Sep 17 00:00:00 2001 From: rkorsak Date: Wed, 16 Nov 2016 14:43:36 -0500 Subject: [PATCH 085/147] Removed the mock API --- mock_api/app.js | 58 ------- mock_api/bin/www | 90 ---------- mock_api/mockproducts.js | 146 ----------------- mock_api/package.json | 21 --- ...eric-VentureOne-EMV-flat-244x154-06-15.png | Bin 23386 -> 0 bytes ...ericEMV-Venture-EMV-flat-244x154-06-15.png | Bin 21775 -> 0 bytes ...0-MC-Blue-Steel-EMV-flat-244x154-06-15.png | Bin 16768 -> 0 bytes .../images/www-venture-visa-sig-flat-9-14.png | Bin 21550 -> 0 bytes mock_api/routes/oauth.js | 72 -------- mock_api/routes/prequal.js | 154 ------------------ mock_api/routes/products.js | 98 ----------- mock_api/views/authorize.jade | 9 - mock_api/views/error.jade | 19 --- mock_api/views/layout.jade | 21 --- 14 files changed, 688 deletions(-) delete mode 100644 mock_api/app.js delete mode 100644 mock_api/bin/www delete mode 100644 mock_api/mockproducts.js delete mode 100644 mock_api/package.json delete mode 100644 mock_api/public/images/JB16760-Generic-VentureOne-EMV-flat-244x154-06-15.png delete mode 100644 mock_api/public/images/JB16760-GenericEMV-Venture-EMV-flat-244x154-06-15.png delete mode 100644 mock_api/public/images/JB16760-MC-Blue-Steel-EMV-flat-244x154-06-15.png delete mode 100644 mock_api/public/images/www-venture-visa-sig-flat-9-14.png delete mode 100644 mock_api/routes/oauth.js delete mode 100644 mock_api/routes/prequal.js delete mode 100644 mock_api/routes/products.js delete mode 100644 mock_api/views/authorize.jade delete mode 100644 mock_api/views/error.jade delete mode 100644 mock_api/views/layout.jade diff --git a/mock_api/app.js b/mock_api/app.js deleted file mode 100644 index f4dfc0c..0000000 --- a/mock_api/app.js +++ /dev/null @@ -1,58 +0,0 @@ -var express = require('express') -var path = require('path') -var logger = require('morgan') -var cookieParser = require('cookie-parser') -var bodyParser = require('body-parser') - -var prequal = require('./routes/prequal') -var oauth = require('./routes/oauth') -var products = require('./routes/products') - -var app = express() - -// view engine setup -app.set('views', path.join(__dirname, 'views')) -app.set('view engine', 'jade') - -app.use(logger('dev')) -app.use(bodyParser.json()) -app.use(bodyParser.urlencoded({ extended: false })) -app.use(cookieParser()) -app.use(express.static(path.join(__dirname, 'public'))) - -app.use('/', prequal) -app.use('/', products) -app.use('/oauth/', oauth) - -// catch 404 and forward to error handler -app.use(function (req, res, next) { - var err = new Error('Not Found') - err.status = 404 - next(err) -}) - -// error handlers - -// development error handler -// will print stacktrace -if (app.get('env') === 'development') { - app.use(function (err, req, res, next) { - res.status(err.status || 500) - res.render('error', { - message: err.message, - error: err - }) - }) -} - -// production error handler -// no stacktraces leaked to user -app.use(function (err, req, res, next) { - res.status(err.status || 500) - res.render('error', { - message: err.message, - error: {} - }) -}) - -module.exports = app diff --git a/mock_api/bin/www b/mock_api/bin/www deleted file mode 100644 index ec0dad6..0000000 --- a/mock_api/bin/www +++ /dev/null @@ -1,90 +0,0 @@ -#!/usr/bin/env node - -/** - * Module dependencies. - */ - -var app = require('../app') -var debug = require('debug')('credit-offers_mock_api:server') -var http = require('http') - -/** - * Get port from environment and store in Express. - */ - -var port = normalizePort(process.env.PORT || '3002') -app.set('port', port) - -/** - * Create HTTP server. - */ - -var server = http.createServer(app) - -/** - * Listen on provided port, on all network interfaces. - */ - -server.listen(port) -server.on('error', onError) -server.on('listening', onListening) - -/** - * Normalize a port into a number, string, or false. - */ - -function normalizePort (val) { - var port = parseInt(val, 10) - - if (isNaN(port)) { - // named pipe - return val - } - - if (port >= 0) { - // port number - return port - } - - return false -} - -/** - * Event listener for HTTP server "error" event. - */ - -function onError (error) { - if (error.syscall !== 'listen') { - throw error - } - - var bind = typeof port === 'string' - ? 'Pipe ' + port - : 'Port ' + port - - // handle specific listen errors with friendly messages - switch (error.code) { - case 'EACCES': - console.error(bind + ' requires elevated privileges') - process.exit(1) - break - case 'EADDRINUSE': - console.error(bind + ' is already in use') - process.exit(1) - break - default: - throw error - } -} - -/** - * Event listener for HTTP server "listening" event. - */ - -function onListening () { - var addr = server.address() - var bind = typeof addr === 'string' - ? 'pipe ' + addr - : 'port ' + addr.port - debug('Listening on ' + bind) -} diff --git a/mock_api/mockproducts.js b/mock_api/mockproducts.js deleted file mode 100644 index 9fc2822..0000000 --- a/mock_api/mockproducts.js +++ /dev/null @@ -1,146 +0,0 @@ -var _ = require('lodash') -var format = require('util').format -var moment = require('moment') -var debug = require('debug')('credit-offers_mock_api:mockproducts') -var loremIpsum = require('lorem-ipsum') - -function multiSample (items, minCount, maxCount) { - if (maxCount === undefined) { - maxCount = minCount - minCount = 0 - } - return _.sampleSize(items, _.random(minCount, maxCount)) -} - -function numberFormat (formatString, max, sampleSize) { - var nums = multiSample(_.range(max), sampleSize) - return _.map(nums, _.partial(format, formatString)) -} - -// Dynamic product properties to be built per product -var randomizeProduct = function (product) { - var activeFrom = moment().startOf('day').subtract(_.random(5), 'years'), - processingNetwork = _.sample(['Visa', 'MasterCard']), - productDisplayName = format('Fake %s %s %s', product.productType, processingNetwork, product.productId), - imageFile = _.sample([ - 'JB16760-GenericEMV-Venture-EMV-flat-244x154-06-15.png', - 'JB16760-Generic-VentureOne-EMV-flat-244x154-06-15.png', - 'JB16760-MC-Blue-Steel-EMV-flat-244x154-06-15.png', - 'www-venture-visa-sig-flat-9-14.png' - ]), - // Fees & APR - initialFee = _.random(0, 100), - fullFee = initialFee + _.random(50), - initialApr = _.random(0, 50), - fullAprLow = initialApr + _.random(9), - fullAprLowFraction = _.random(99), - fullAprHigh = fullAprLow + 10 + _.random(9), - fullAprHighFraction = _.random(99), - aprMonthLimit = _.random(2, 10), - // Mile rewards - milesPerDollar = _.random(2, 4), - bonusMiles = format('%d0,000', _.random(1, 5)), - rewardSpendingMin = format('%d,000', _.random(1, 3)), - rewardMonthLimit = _.random(3, 10).toString(), - marketingCopy = _.map(_.range(25), function () { - return loremIpsum({ - count: _.random(5, 50), - units: 'words', - format: 'plain' - }) - }) - - var newValues = { - productDisplayName: productDisplayName, - activeFrom: activeFrom.format(), - activeTo: activeFrom.add(_.random(6, 12), 'years').format(), - publishedDate: activeFrom.subtract(_.random(1, 20), 'days').format(), - applyNowLink: format('https://capitalone.com?fake=applyNowLink&productId=%s', product.productId), - brand: _.sample(['Cool Brand', 'Great Brand', 'Awesome Brand']), - images: [{ - height: 154, - width: 244, - alternateText: format('Apply now for the %s Card', productDisplayName), - url: 'http://localhost:3002/images/' + imageFile, - imageType: 'CardName' - }], - categoryTags: multiSample([ - 'Rewards (travel rewards, airlines)', - 'Excellent', - 'Good', - 'Capital One', - 'MasterCard', - 'Low Intro Rate', - 'Zero Percent', - 'Low Interest' - ], 4), - productKeywords: multiSample([ - 'Capital One', - 'Card', - 'Credit', - 'Credit Card', - 'VentureOne', - 'Rewards' - ], 3), - marketingCopy: multiSample(marketingCopy, 1, 10), - processingNetwork: processingNetwork, - creditRating: multiSample([ - 'Excellent', - 'Good', - 'Average', - 'Rebuilding' - ], 2), - rewardsType: _.sample([null, 'Miles', 'Points']), - primaryBenefitDescription: format('Earn unlimited %d miles per dollar on every purchase. Plus, earn %s bonus miles once you spend $%s on purchases within the first %d months.', - milesPerDollar, bonusMiles, rewardSpendingMin, rewardMonthLimit), - balanceTransferFeeDescription: format('$%d', initialFee), - introBalanceTransferAPRDescription: format('%d%%', initialApr), - balanceTransferAPRDescription: format('%d%% intro APR for %d months; %d.%d%% to %d.%d%% variable APR after that', - initialApr, - aprMonthLimit, - fullAprLow, fullAprLowFraction, - fullAprHigh, fullAprHighFraction), - purchaseAPRDescription: format('%d.%d%% to %d.%d%% variable APR after the first %d months', - fullAprLow, fullAprLowFraction, - fullAprHigh, fullAprHighFraction, - aprMonthLimit), - annualMembershipFeeDescription: format('$%d intro for first year; $%d after that', initialFee, fullFee), - rewardsRateDescription: format(''), - foreignTransactionFeeDescription: '', - fraudCoverageDescription: '', - latePaymentDescription: '', - penaltyAPRDescription: '', - cashAdvanceAPRDescription: format('%d.%d%% (Variable)', fullAprHigh, fullAprHighFraction), - generalDescription: '', - promotionalDescriptions: [] - } - return _.defaults(product, newValues) -} - -// Generate a collection of fake products based on a spec -// Spec is an object keyed by product type, containing the number of products for that type -function generate (spec) { - var totalCount = _.sum(_.values(spec)) - var ids = _.range(1, totalCount + 1) - var productTypes = _(spec) - // Generate the appropriate number of each product type - .flatMap(function (count, productType) { - return _().range(count).map(() => productType).value() - }) - // Randomize the categories to distribute them evenly across IDs - .shuffle() - - var startingProducts = productTypes.zipWith(ids, function (productType, id) { - return { - productId: id.toString(), - productType: productType - } - }) - - return startingProducts - .map(randomizeProduct) - .sortBy(function (product) { return parseInt(product.productId) }) - .value() -} - -module.exports = generate diff --git a/mock_api/package.json b/mock_api/package.json deleted file mode 100644 index 44c605b..0000000 --- a/mock_api/package.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "credit_offers_mock_api", - "version": "0.2.0", - "private": true, - "scripts": { - "start": "node ./bin/www" - }, - "dependencies": { - "body-parser": "~1.13.2", - "cookie-parser": "~1.3.5", - "debug": "~2.2.0", - "express": "~4.13.1", - "jade": "^1.11.0", - "lodash": "4.16.4", - "lorem-ipsum": "1.0.3", - "moment": "2.15.2", - "morgan": "~1.6.1", - "node-uuid": "1.4.7", - "request": "^2.69.0" - } -} diff --git a/mock_api/public/images/JB16760-Generic-VentureOne-EMV-flat-244x154-06-15.png b/mock_api/public/images/JB16760-Generic-VentureOne-EMV-flat-244x154-06-15.png deleted file mode 100644 index 0e18f499a35f9cad888f9608b3dd1804b6fbfb34..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 23386 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 diff --git a/mock_api/public/images/www-venture-visa-sig-flat-9-14.png b/mock_api/public/images/www-venture-visa-sig-flat-9-14.png deleted file mode 100644 index d03e705c319bda101b196bd68bd08b02d37576c0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 21550 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

{4sp3#$8Y0 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 Date: Wed, 16 Nov 2016 14:53:32 -0500 Subject: [PATCH 086/147] Fixed a README mistake in the run instructions --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 24676eb..72fd45f 100644 --- a/README.md +++ b/README.md @@ -31,8 +31,7 @@ All other dependencies are loaded with [npm](https://www.npmjs.com/). All depend config.js contains information specific to your app, such as your client_id and client_secret. Create this file by copying [config.js.sample](/credit-offers/config.js.sample). Be careful not to put config.js into version control. ### Start the app -From the project root: -`cd credit-offers` +From the project root: `npm install` `npm start` From 0f26f54b2b6fe27e2267895801865d5b518cae2a Mon Sep 17 00:00:00 2001 From: rkorsak Date: Fri, 18 Nov 2016 13:12:22 -0500 Subject: [PATCH 087/147] Added prequal summary to client --- creditoffers/prequalification.js | 33 ++++++++++++++++++++++++++++++++ package.json | 1 + 2 files changed, 34 insertions(+) diff --git a/creditoffers/prequalification.js b/creditoffers/prequalification.js index 3e36115..9b6cb2b 100644 --- a/creditoffers/prequalification.js +++ b/creditoffers/prequalification.js @@ -15,6 +15,8 @@ See the License for the specific language governing permissions and limitations var request = require('request') var debug = require('debug')('credit-offers:api-client') +var moment = require('moment') +var _ = require('lodash') /** * Contains all functions for interacting with the credit offer prequalification API @@ -60,3 +62,34 @@ Prequalification.prototype.acknowledge = function acknowledge (prequalificationI body: { 'hasBeenAcknowledged': true } }, callback) } + +/** + * Retrieves a set of summary info about your client's usage of the prequalification API + */ +Prequalification.prototype.getSummary = function getSummary (options, callback) { + var defaults = { + fromDate: null, + toDate: null, + minIncome: null, + maxIncome: null + } + + options = _.defaults({}, options, defaults) + var dateFormat = 'YYYY-MM-DD', + fromDate = options.fromDate && moment(options.fromDate).format(dateFormat), + toDate = options.toDate && moment(options.toDate).format(dateFormat), + // Build the query string values, dropping any nulls + query = _.omitBy({ + fromDate: fromDate, + toDate: toDate, + minIncome: options.minIncome, + maxIncome: options.maxIncome + }, _.isNull) + + this.client.sendRequest({ + url: '/credit-offers/prequalifications-summary', + useOAuth: true, + method: 'GET', + qs: query + }, callback) +} diff --git a/package.json b/package.json index af6c8e0..6c94d72 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "html-entities": "1.2.0", "jade": "~1.11.0", "lodash": "~4.1.0", + "moment": "2.16.0", "morgan": "~1.6.1", "request": "~2.69.0", "sanitize-html": "1.13.0", From e54f183f3002eee79bb2917beaa5c344fdf71478 Mon Sep 17 00:00:00 2001 From: rkorsak Date: Fri, 18 Nov 2016 13:12:39 -0500 Subject: [PATCH 088/147] Added prequal summary to UI --- public/css/style.css | 24 ++++++++++++++ routes/offers.js | 15 +++++++++ views/offers.jade | 78 ++++++++++++++++++++++++++++++-------------- 3 files changed, 92 insertions(+), 25 deletions(-) diff --git a/public/css/style.css b/public/css/style.css index 2e2d43c..1774bed 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -98,6 +98,30 @@ body { .btn i { margin-right: 10px; } +#prequal-offers > .cards { + margin-bottom: 40px; } + +footer#prequal-stats-summary { + display: flex; + flex-flow: row nowrap; + align-items: center; + height: 40px; + padding-left: 15px; + padding-right: 15px; + background-color: #666; + color: #ddd; + box-shadow: inset 0 10px 10px -10px rgba(0,0,0,0.5); } + #prequal-stats-summary i { + margin-right: 10px; } + #prequal-stats-summary .stat-text { + display: flex; + flex-grow: 1; + flex-flow: row nowrap; } + #prequal-stats-summary .stat:not(:last-child) { + margin-right: 15px; } + #prequal-stats-summary .stat-title { + font-weight: bold; } + /* Bootstrap overrides */ .navbar-default { border: 0; diff --git a/routes/offers.js b/routes/offers.js index 35a3baf..0073bec 100644 --- a/routes/offers.js +++ b/routes/offers.js @@ -129,5 +129,20 @@ module.exports = function (client) { }) }) + // GET prequalification summary data for this client + router.get('/summary', function (req, res, next) { + debug('Getting prequal summary data') + + // Get unfiltered summary + client.prequalification.getSummary({}, function (err, response) { + if (err) { + debug('Error in API call', err) + res.status(500).send() + return + } + res.json(response) + }) + }) + return router } diff --git a/views/offers.jade b/views/offers.jade index ea45a54..edb86f3 100644 --- a/views/offers.jade +++ b/views/offers.jade @@ -14,31 +14,35 @@ extends ./layout block content - div.header - div.container - if error - span.error= error - else if isPrequalified - h2 You prequalify for the following offers: - else if products && products.length - h2 We're sorry, we can't prequalify you based on the information that you provided, but you can check out these great offers: - else - h2 We weren't able to find any offers + div#prequal-offers + div.header + div.container + if error + span.error= error + else if isPrequalified + h2 You prequalify for the following offers: + else if products && products.length + h2 We're sorry, we can't prequalify you based on the information that you provided, but you can check out these great offers: + else + h2 We weren't able to find any offers - div.cards.container - each card in products - div.card-info - div.main-info - div.details - p.card-title - +cardNameLink(card) - div.summary - +cardNameImage(card) - +marketingCopy(card) - div.apply - +applyButton(card) - p with CapitalOne - sup ® + div.cards.container + each card in products + div.card-info + div.main-info + div.details + p.card-title + +cardNameLink(card) + div.summary + +cardNameImage(card) + +marketingCopy(card) + div.apply + +applyButton(card) + p with CapitalOne + sup ® + footer#prequal-stats-summary.navbar-fixed-bottom + i.fa.fa-pie-chart(aria-hidden="true") + div.stat-text Loading Stats... block scripts script. @@ -46,7 +50,7 @@ block scripts var prequalificationId = '#{prequalificationId}' if (!prequalificationId) { console.warn('Displayed offers without a prequalification ID') - return; + return } // Tell the server that we have displayed the products to the user @@ -58,3 +62,27 @@ block scripts console.warn('Failed to acknowledge prequalification products: ' + err) }) }) + + $(function () { + function makeStat(title, value) { + var stat = $('

').addClass('stat') + stat.append($('').addClass('stat-title').html(title + ': ')) + stat.append($('').html(value)) + return stat + } + + $.getJSON('/offers/summary') + .done(function (data) { + var parent = $('#prequal-stats-summary .stat-text').html(''), + stats = [ + makeStat('Consumer Cards', data.consumerCardCount), + makeStat('Non-PreQualified', data.nonPrequalifiedCount), + makeStat('PreQualified', data.prequalifiedCount), + makeStat('Total', data.total) + ] + parent.append(stats) + }) + .fail(function () { + $('#prequal-stats-summary .stat-text').html('FAILED to get prequalification summary data!') + }) + }) From 04a2856ebf64c7843e64af0a70925dda84011c34 Mon Sep 17 00:00:00 2001 From: rkorsak Date: Fri, 18 Nov 2016 13:12:49 -0500 Subject: [PATCH 089/147] OAuth debug log --- oauth/index.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/oauth/index.js b/oauth/index.js index 70d3c53..bb2ac67 100644 --- a/oauth/index.js +++ b/oauth/index.js @@ -40,6 +40,8 @@ module.exports = function (options) { } } + debug('Sending OAuth request', reqOptions) + request(reqOptions, function (error, response, body) { if (error) { return callback(error) From eed41ec5565eeb9c7d2803bfe228ff6cc3a3861b Mon Sep 17 00:00:00 2001 From: rkorsak Date: Fri, 18 Nov 2016 13:18:35 -0500 Subject: [PATCH 090/147] Updated the README with prequal summary info --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 72fd45f..8d93367 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ This reference app illustrates the use of the Credit Offers API to * Retrieve and display card products (Business or Consumer) using the `/credit-offers/products/cards/{cardType}` endpoint * Collect customer information and retrieve a list of targeted product offers for display using the `/credit-offers/prequalifications` endpoint * Send acknowledgement to Capital One that the targeted product offers have been displayed using the `/credit-offers/prequalifications/{prequalificationId}` endpoint +* Retrieve and display prequalification summary info using the `/credit-offers/prequalifications-summary` endpoint Some additional API features that are **not** directly illustrated by this app include: @@ -40,7 +41,9 @@ From the project root: Navigate to http://localhost:3000. This will retrieve a list of Consumer card products from the API and display simple information about each. From here, you can try a few simple things: * Toggle the card type to 'Business' to request and display a list of business card products from the API - * Click on the 'Find Pre-Qualified Offers' button to launch a simple customer information form and test out the pre-qualification API behavior. + * Click on the 'Find Pre-Qualified Offers' button to launch a simple customer information form and test out the pre-qualification API behavior. The results screen will also perform two asynchronous calls: + * POST to `/credit-offers/prequalifications/{prequalificationId}` to acknowledge that the results were displayed to the customer + * GET from the `/credit-offers/prequalifications-summary` endpoint to display simple pre-qualification statistics at the bottom of the page #### A note about errors From 733d4f3d881f224bb0635c1cb452a1de8179fec9 Mon Sep 17 00:00:00 2001 From: rkorsak Date: Tue, 22 Nov 2016 11:02:43 -0500 Subject: [PATCH 091/147] Switched dependencies to exact versions --- package.json | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/package.json b/package.json index 6c94d72..3681af6 100644 --- a/package.json +++ b/package.json @@ -9,24 +9,24 @@ } ], "dependencies": { - "body-parser": "~1.13.2", + "body-parser": "1.13.3", "bootstrap": "3.3.7", - "cookie-parser": "~1.3.5", - "csurf": "~1.8.3", - "debug": "~2.2.0", - "ejs": "~2.3.3", - "express": "~4.13.1", + "cookie-parser": "1.3.5", + "csurf": "1.8.3", + "debug": "2.2.0", + "ejs": "2.3.4", + "express": "4.13.4", "express-validator": "2.21.0", "font-awesome": "4.7.0", - "helmet": "~1.1.0", + "helmet": "1.1.0", "html-entities": "1.2.0", - "jade": "~1.11.0", - "lodash": "~4.1.0", + "jade": "1.11.0", + "lodash": "4.1.0", "moment": "2.16.0", - "morgan": "~1.6.1", - "request": "~2.69.0", + "morgan": "1.6.1", + "request": "2.69.0", "sanitize-html": "1.13.0", - "serve-favicon": "~2.3.0" + "serve-favicon": "2.3.0" }, "engines": { "node": ">=4.0.0" From 3c75c686aa9249e162156ba6effe233c1047ccf5 Mon Sep 17 00:00:00 2001 From: Jennifer Rondeau Date: Mon, 28 Nov 2016 09:07:40 -0500 Subject: [PATCH 092/147] add config detail in readme --- README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 8d93367..b6e47ca 100644 --- a/README.md +++ b/README.md @@ -29,10 +29,15 @@ All other dependencies are loaded with [npm](https://www.npmjs.com/). All depend * [express](http://expressjs.com/) - Minimalist web framework for Node.js ### config.js -config.js contains information specific to your app, such as your client_id and client_secret. Create this file by copying [config.js.sample](/credit-offers/config.js.sample). Be careful not to put config.js into version control. +You'll need to set up your `config.js` file before you can run the app. + +* Create this file by copying and renaming [config.js.sample](/credit-offers/config.js.sample). Be careful not to put `config.js` into version control. (We've added it to the repository's `.gitignore` for you.) +* Make sure that you've registered an app on [Capital One's developer portal](developer.capitalone.com). +* Edit the `clientID` and `clientSecret` values in `config.js` to specify the **Client ID** and **Client Secret** that were provided when you registered the app. ### Start the app From the project root: + `npm install` `npm start` From 480528c60a73711f8c7f2cffe323a3c7179186f3 Mon Sep 17 00:00:00 2001 From: Jennifer Rondeau Date: Mon, 28 Nov 2016 09:09:18 -0500 Subject: [PATCH 093/147] update oauth token endpoint --- config.js.sample | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.js.sample b/config.js.sample index 9375d60..385ba98 100644 --- a/config.js.sample +++ b/config.js.sample @@ -30,7 +30,7 @@ module.exports = { apiVersion: 2 }, oauth: { - tokenURL: oauthHost + '/oauth/oauth20/token', + tokenURL: oauthHost + '/oauth2/token', // The clientId and clientSecret you received when registering your app. clientID: 'COF_CLIENT_ID', clientSecret: 'COF_CLIENT_SECRET' From 71534efb84e7d8369f9dc17c61082bd79cf39338 Mon Sep 17 00:00:00 2001 From: rkorsak Date: Wed, 30 Nov 2016 15:28:22 -0500 Subject: [PATCH 094/147] Normalize card image sizes --- public/css/style.css | 2 ++ 1 file changed, 2 insertions(+) diff --git a/public/css/style.css b/public/css/style.css index 1774bed..2535c5e 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -49,6 +49,8 @@ body { flex-flow: column nowrap; border: 1px solid #aaa; background-color: #fff; } + .card-info img.card-name { + width: 200px; } .card-info .card-title { font-size: 1.5em; font-weight: 600; } From 94ef8dfc71cdb10a6a804b611310faa6aed0f7e1 Mon Sep 17 00:00:00 2001 From: rkorsak Date: Wed, 30 Nov 2016 15:28:41 -0500 Subject: [PATCH 095/147] Use any available card image for both APIs --- viewmodels/preQualProduct.js | 4 +++- viewmodels/product.js | 4 +++- views/layout.jade | 7 +++++-- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/viewmodels/preQualProduct.js b/viewmodels/preQualProduct.js index e9f2de5..8ca8ca8 100644 --- a/viewmodels/preQualProduct.js +++ b/viewmodels/preQualProduct.js @@ -28,7 +28,9 @@ module.exports = function preQualProduct (apiProduct) { viewModel.productDisplayName = sanitize(apiProduct.productName || '???') viewModel.images = { - cardName: _.find(apiProduct.images, { imageType: 'CardName' }) + cardName: _.find(apiProduct.images, { imageType: 'CardName' }), + cardArt: _.find(apiProduct.images, { imageType: 'CardArt' }), + banner: _.find(apiProduct.images, { imageType: 'Banner' }) } // Normalize to the keys used by the products API viewModel.primaryBenefitDescription = sanitize(_.get(apiProduct, 'terms.primaryBenefit')) diff --git a/viewmodels/product.js b/viewmodels/product.js index ce416de..0264b7f 100644 --- a/viewmodels/product.js +++ b/viewmodels/product.js @@ -50,7 +50,9 @@ module.exports = function product (apiProduct) { viewModel.productDisplayName = sanitize(apiProduct.productDisplayName || '???') viewModel.images = { - cardName: _.find(apiProduct.images, { imageType: 'CardName' }) + cardName: _.find(apiProduct.images, { type: 'CardName' }), + cardArt: _.find(apiProduct.images, { type: 'CardArt' }), + banner: _.find(apiProduct.images, { type: 'Banner' }) } viewModel.additionalInformationUrl = _.get(apiProduct, 'links.self.href') diff --git a/views/layout.jade b/views/layout.jade index ab11587..077be1d 100644 --- a/views/layout.jade +++ b/views/layout.jade @@ -20,8 +20,11 @@ mixin cardNameLink(card) span !{card.productDisplayName} mixin cardNameImage(card) - if card.images && card.images.cardName - img.card-name(src=card.images.cardName.url, alt=card.images.cardName.alternateText) + - console.log(card) + - var image = card.images && (card.images.cardName || card.images.cardArt || card.images.banner) + - console.log(image) + if image + img.card-name(src=image.url, alt=image.alternateText) else img.card-name(src='/images/default-card.png') From 7b83011edfae689050f2634828590f68c0ba35b7 Mon Sep 17 00:00:00 2001 From: rkorsak Date: Wed, 30 Nov 2016 16:29:34 -0500 Subject: [PATCH 096/147] Removed extra logging in the view --- views/layout.jade | 2 -- 1 file changed, 2 deletions(-) diff --git a/views/layout.jade b/views/layout.jade index 077be1d..88592e1 100644 --- a/views/layout.jade +++ b/views/layout.jade @@ -20,9 +20,7 @@ mixin cardNameLink(card) span !{card.productDisplayName} mixin cardNameImage(card) - - console.log(card) - var image = card.images && (card.images.cardName || card.images.cardArt || card.images.banner) - - console.log(image) if image img.card-name(src=image.url, alt=image.alternateText) else From c4bc3c4d1d5228df9102168a6671a00955aea023 Mon Sep 17 00:00:00 2001 From: rkorsak Date: Wed, 30 Nov 2016 16:30:25 -0500 Subject: [PATCH 097/147] Prefer card art images over card name --- views/layout.jade | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/views/layout.jade b/views/layout.jade index 88592e1..ebc69f2 100644 --- a/views/layout.jade +++ b/views/layout.jade @@ -20,7 +20,7 @@ mixin cardNameLink(card) span !{card.productDisplayName} mixin cardNameImage(card) - - var image = card.images && (card.images.cardName || card.images.cardArt || card.images.banner) + - var image = card.images && (card.images.cardArt || card.images.cardName || card.images.banner) if image img.card-name(src=image.url, alt=image.alternateText) else From f5f7c738fa3534b5f91d78b028c9f9a775034e86 Mon Sep 17 00:00:00 2001 From: Ryan Korsak Date: Thu, 1 Dec 2016 10:38:37 -0500 Subject: [PATCH 098/147] Changed 'type' back to 'imageType' for products The use of 'type' was a bug in the API --- viewmodels/product.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/viewmodels/product.js b/viewmodels/product.js index 0264b7f..c50a527 100644 --- a/viewmodels/product.js +++ b/viewmodels/product.js @@ -50,9 +50,9 @@ module.exports = function product (apiProduct) { viewModel.productDisplayName = sanitize(apiProduct.productDisplayName || '???') viewModel.images = { - cardName: _.find(apiProduct.images, { type: 'CardName' }), - cardArt: _.find(apiProduct.images, { type: 'CardArt' }), - banner: _.find(apiProduct.images, { type: 'Banner' }) + cardName: _.find(apiProduct.images, { imageType: 'CardName' }), + cardArt: _.find(apiProduct.images, { imageType: 'CardArt' }), + banner: _.find(apiProduct.images, { imageType: 'Banner' }) } viewModel.additionalInformationUrl = _.get(apiProduct, 'links.self.href') From 73986dae57a1aa21bbdfc2d7c96a6d9f49ddafc6 Mon Sep 17 00:00:00 2001 From: Ryan Korsak Date: Thu, 1 Dec 2016 15:36:15 -0500 Subject: [PATCH 099/147] Made address type optional --- views/includes/customer-form.jade | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/views/includes/customer-form.jade b/views/includes/customer-form.jade index ca9fc72..df06157 100644 --- a/views/includes/customer-form.jade +++ b/views/includes/customer-form.jade @@ -100,7 +100,8 @@ mixin stateOptions() +select('stateCode', 'State', true) +stateOptions() +textinput('postalCode', 'Zip Code', true) -+select('addressType', 'Address Type', true) ++select('addressType', 'Address Type') + option(value='') option(value='Home') Home option(value='Business') Business +textinput('taxId', 'Last Four Digits of SSN', true) From 3c3fed9fc94c1e9ef2a2a30edcb73bac0c94e669 Mon Sep 17 00:00:00 2001 From: Ryan Korsak Date: Fri, 9 Dec 2016 11:29:16 -0500 Subject: [PATCH 100/147] Add acknowledgement indicator to the app --- public/css/style.css | 7 +++++++ views/offers.jade | 14 ++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/public/css/style.css b/public/css/style.css index 2535c5e..1871ce7 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -124,6 +124,13 @@ footer#prequal-stats-summary { #prequal-stats-summary .stat-title { font-weight: bold; } +.acknowledgement .status-icon { + margin-left: 5px; } + .acknowledgement .status-icon.success { + color: #61b961; } + .acknowledgement .status-icon.failure { + color: #e42626; } + /* Bootstrap overrides */ .navbar-default { border: 0; diff --git a/views/offers.jade b/views/offers.jade index edb86f3..51ee65a 100644 --- a/views/offers.jade +++ b/views/offers.jade @@ -43,6 +43,9 @@ block content footer#prequal-stats-summary.navbar-fixed-bottom i.fa.fa-pie-chart(aria-hidden="true") div.stat-text Loading Stats... + div.acknowledgement + span.status-text Acknowledging... + i.status-icon.fa.fa-circle-o block scripts script. @@ -57,9 +60,20 @@ block scripts $.post('/offers/acknowledge/' + encodeURIComponent(prequalificationId)) .done(function () { console.log('Successfully acknowledged prequalification products') + $('.acknowledgement .status-icon').addClass('success') + $('.acknowledgement .status-text').text('Acknowledged') }) .fail(function (res, st, err) { console.warn('Failed to acknowledge prequalification products: ' + err) + $('.acknowledgement .status-icon').addClass('failure') + $('.acknowledgement .status-text').text('Acknowledgement Failed') + $('.acknowledgement') + .attr('data-toggle', 'tooltip') + .attr('title', 'err') + .tooltip() + }) + .always(function () { + $('.acknowledgement .status-icon').removeClass('fa-circle-o').addClass('fa-circle') }) }) From c09222e58eb4c79344732f643db56d23935d0f9c Mon Sep 17 00:00:00 2001 From: Ryan Korsak Date: Fri, 9 Dec 2016 11:53:50 -0500 Subject: [PATCH 101/147] Added option to view all cards --- routes/index.js | 34 +++++++++++++++++++++++----------- views/index.jade | 6 +++--- 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/routes/index.js b/routes/index.js index ac6d2f9..46613b0 100644 --- a/routes/index.js +++ b/routes/index.js @@ -23,16 +23,21 @@ module.exports = function (client) { var router = express.Router() // The supported card types - var cardTypes = [ - { - name: 'consumer', - display: 'Consumer Cards' + var allType = { + name: 'all', + display: 'All Cards' }, - { - name: 'business', - display: 'Business Cards' - } - ] + cardTypes = [ + allType, + { + name: 'consumer', + display: 'Consumer Cards' + }, + { + name: 'business', + display: 'Business Cards' + } + ] // How many products to pull at a time var productCount = 10 @@ -40,12 +45,13 @@ module.exports = function (client) { /* GET home page. */ router.get('/', csrfProtection, function (req, res, next) { var requestedCardType = _.find(cardTypes, { name: req.query.cardType }) + if (!requestedCardType) { res.redirect('/?cardType=' + cardTypes[0].name) return } - client.products.getCards(requestedCardType.name, { limit: productCount }, function (err, data) { + var onComplete = function (err, data) { if (err) { return next(err) } cards = _.map(_.get(data, 'products', []), productViewModel) @@ -56,7 +62,13 @@ module.exports = function (client) { cardTypes: cardTypes, cards: cards }) - }) + } + + if (requestedCardType === allType) { + client.products.getAllCards({ limit: productCount }, onComplete) + } else { + client.products.getCards(requestedCardType.name, { limit: productCount }, onComplete) + } }) return router diff --git a/views/index.jade b/views/index.jade index 6602c6b..0094764 100644 --- a/views/index.jade +++ b/views/index.jade @@ -22,18 +22,18 @@ block content div.header div.container div.row - div.col-md-8 + div.col-md-7 h2 CapitalOne sup ® | Credit Cards p These are some of the cards on offer from CapitalOne sup ® | . - div.filters.col-md-4 + div.filters.col-md-5 div.pull-right div.btn-group(role="group", aria-label="Card Types") each cardType in cardTypes - a.btn.btn-primary(href='/?cardType='+cardType.name, disabled=currentCardType===cardType.name)= cardType.display + a.btn.btn-sm.btn-primary(href='/?cardType='+cardType.name, disabled=currentCardType===cardType.name)= cardType.display div.cards.container each card in cards div.card-info From 10b0f252a25251f2465c96378929058284a69e4d Mon Sep 17 00:00:00 2001 From: Ryan Korsak Date: Fri, 9 Dec 2016 12:21:46 -0500 Subject: [PATCH 102/147] Add keyword tooltip on image hover --- views/index.jade | 8 +++++++- views/layout.jade | 4 ++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/views/index.jade b/views/index.jade index 0094764..d1afcaf 100644 --- a/views/index.jade +++ b/views/index.jade @@ -42,7 +42,7 @@ block content p.card-title +cardNameLink(card) div.summary - +cardNameImage(card) + +cardNameImage(card)(data-toggle='tooltip', title='#{(card.productKeywords || []).join(", ")}') +marketingCopy(card) div.apply +applyButton(card) @@ -67,3 +67,9 @@ block modals | Close button.btn.btn-primary(type='submit') | See Offers + +block scripts + script. + $(function () { + $('.card-info [data-toggle="tooltip"]').tooltip({ delay: 200, placement: 'right' }) + }) diff --git a/views/layout.jade b/views/layout.jade index ebc69f2..3262fa4 100644 --- a/views/layout.jade +++ b/views/layout.jade @@ -22,9 +22,9 @@ mixin cardNameLink(card) mixin cardNameImage(card) - var image = card.images && (card.images.cardArt || card.images.cardName || card.images.banner) if image - img.card-name(src=image.url, alt=image.alternateText) + img.card-name(src=image.url, alt=image.alternateText)&attributes(attributes) else - img.card-name(src='/images/default-card.png') + img.card-name(src='/images/default-card.png')&attributes(attributes) mixin applyButton(card) a.btn.btn.btn-lg.btn-success(href=card.applyNowLink) From f5ccfe2d4debb50a9eb4b8e66707bc2d0878f873 Mon Sep 17 00:00:00 2001 From: Ryan Korsak Date: Thu, 15 Dec 2016 16:42:09 -0500 Subject: [PATCH 103/147] Add additional info to each product --- public/css/style.css | 15 ++++++++++++--- views/index.jade | 15 +++++++++++++++ 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/public/css/style.css b/public/css/style.css index 1871ce7..badef49 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -74,11 +74,20 @@ body { .card-info .additional-info { display: flex; flex-flow: row nowrap; - align-items: stretch; - background-color: #bbb; - box-shadow: inset 0 6px 6px -4px rgba(0,0,0,0.25); } + align-items: stretch; } .card-info .additional-info > * { flex-grow: 1; } + .card-info .info .info-label { + height: 54px; + background-color: #0a4469; + text-align: center; + padding: 10px 20px; + color: white; } + .card-info .info:not(:last-child) .info-label { + border-right: 1px solid #fff; } + .card-info .info .info-value { + text-align: center; + padding: 10px; } .marketing-copy > ul:first-child { margin-bottom: 0; } diff --git a/views/index.jade b/views/index.jade index d1afcaf..54083e9 100644 --- a/views/index.jade +++ b/views/index.jade @@ -49,6 +49,21 @@ block content p with CapitalOne sup ® div.additional-info + div.info + div.info-label Purchases Intro APR + div.info-value= card.introPurchaseAPRDescription || 'N/A' + div.info + div.info-label Balance Transfers Intro APR + div.info-value= card.balanceTransferAPRDescription || 'N/A' + div.info + div.info-label Regular APR + div.info-value= card.purchaseAPRDescription || 'N/A' + div.info + div.info-label Annual Fee + div.info-value= card.annualMembershipFeeDescription || 'N/A' + div.info + div.info-label Credit Needed + div.info-value= (card.creditRating || []).join(', ' ) || 'N/A' block modals div#customer-info.modal.fade(tabindex='-1', role='dialog') From ca3a24ba9fa117e6f5bd8c6a8d9c25f6f387886c Mon Sep 17 00:00:00 2001 From: Ryan Korsak Date: Tue, 3 Jan 2017 11:41:51 -0500 Subject: [PATCH 104/147] Added modal to display raw JSON per product --- public/css/style.css | 21 +++++++++++++++++++-- viewmodels/product.js | 3 +++ views/index.jade | 27 ++++++++++++++++++++++++--- 3 files changed, 46 insertions(+), 5 deletions(-) diff --git a/public/css/style.css b/public/css/style.css index badef49..9662da7 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -78,16 +78,30 @@ body { .card-info .additional-info > * { flex-grow: 1; } .card-info .info .info-label { + display: flex; + flex-flow: column nowrap; + justify-content: center; height: 54px; - background-color: #0a4469; - text-align: center; padding: 10px 20px; + text-align: center; + background-color: #0a4469; color: white; } .card-info .info:not(:last-child) .info-label { border-right: 1px solid #fff; } .card-info .info .info-value { text-align: center; padding: 10px; } + .card-info .json-toggle { + font-size: 0.5em; + text-decoration: none; + background-color: #eee; + padding: 0.5em; + border-radius: 0.2em; + color: #888; } + .card-info .json-toggle:hover { + text-decoration: none; + background-color: #efefef; + color: #333; } .marketing-copy > ul:first-child { margin-bottom: 0; } @@ -106,6 +120,9 @@ body { font-family: FontAwesome; content: " \f077"; } +#card-json .raw-json { + max-height: 400px; } + .btn i { margin-right: 10px; } diff --git a/viewmodels/product.js b/viewmodels/product.js index c50a527..9578186 100644 --- a/viewmodels/product.js +++ b/viewmodels/product.js @@ -61,5 +61,8 @@ module.exports = function product (apiProduct) { viewModel.mainMarketingCopy = _.take(marketingCopy, 2) viewModel.extraMarketingCopy = _.drop(marketingCopy, 2) + // Store the raw JSON value to display as reference in the UI + viewModel.rawJSON = JSON.stringify(apiProduct) + return viewModel } diff --git a/views/index.jade b/views/index.jade index 54083e9..6d4e61f 100644 --- a/views/index.jade +++ b/views/index.jade @@ -36,11 +36,13 @@ block content a.btn.btn-sm.btn-primary(href='/?cardType='+cardType.name, disabled=currentCardType===cardType.name)= cardType.display div.cards.container each card in cards - div.card-info + // NOTE: In a real app, avoid storing truth in the DOM! + div.card-info(data-raw-json="#{card.rawJSON}") div.main-info div.details p.card-title +cardNameLink(card) + a.json-toggle.pull-right(href="#", title="See raw JSON") {...} div.summary +cardNameImage(card)(data-toggle='tooltip', title='#{(card.productKeywords || []).join(", ")}') +marketingCopy(card) @@ -82,9 +84,28 @@ block modals | Close button.btn.btn-primary(type='submit') | See Offers + div#card-json.modal.fade(tabindex='-1', role='dialog') + div.modal-dialog + div.modal-content + div.modal-header + button.close(type='button', data-dismiss='modal', aria-label='close') + span(aria-hidden='true') × + h4.modal-title Raw Card JSON + div.modal-body + pre.raw-json block scripts script. $(function () { - $('.card-info [data-toggle="tooltip"]').tooltip({ delay: 200, placement: 'right' }) - }) + $('.card-info [data-toggle="tooltip"]').tooltip({ delay: 200, placement: 'right' }); + }); + $(function () { + $('.card-info .json-toggle').click(function (evt) { + var card = $(this).closest('.card-info'), + json = JSON.stringify(card.data('rawJson'), null, 2); + evt.preventDefault(); + + $('#card-json .raw-json').text(json); + $('#card-json').modal('show'); + }); + }); \ No newline at end of file From 63fb0a8cfa3cf272749cb05340c5b13f31194374 Mon Sep 17 00:00:00 2001 From: Ryan Korsak Date: Tue, 3 Jan 2017 12:00:29 -0500 Subject: [PATCH 105/147] Added raw JSON modal to pre-qual results --- viewmodels/preQualProduct.js | 3 +++ views/includes/raw-json-modal.jade | 22 ++++++++++++++++++++++ views/index.jade | 12 ++---------- views/offers.jade | 17 ++++++++++++++++- 4 files changed, 43 insertions(+), 11 deletions(-) create mode 100644 views/includes/raw-json-modal.jade diff --git a/viewmodels/preQualProduct.js b/viewmodels/preQualProduct.js index 8ca8ca8..782209b 100644 --- a/viewmodels/preQualProduct.js +++ b/viewmodels/preQualProduct.js @@ -46,5 +46,8 @@ module.exports = function preQualProduct (apiProduct) { viewModel.mainMarketingCopy = _.take(marketingCopy, 2) viewModel.extraMarketingCopy = _.drop(marketingCopy, 2) + // Store the raw JSON value to display as reference in the UI + viewModel.rawJSON = JSON.stringify(apiProduct) + return viewModel } diff --git a/views/includes/raw-json-modal.jade b/views/includes/raw-json-modal.jade new file mode 100644 index 0000000..f1f16f4 --- /dev/null +++ b/views/includes/raw-json-modal.jade @@ -0,0 +1,22 @@ +//- Copyright 2016 Capital One Services, LLC +//- +//- Licensed under the Apache License, Version 2.0 (the "License"); +//- you may not use this file except in compliance with the License. +//- You may obtain a copy of the License at +//- +//- http://www.apache.org/licenses/LICENSE-2.0 +//- +//- Unless required by applicable law or agreed to in writing, software +//- distributed under the License is distributed on an "AS IS" BASIS, +//- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//- See the License for the specific language governing permissions and limitations under the License. + +div#card-json.modal.fade(tabindex='-1', role='dialog') + div.modal-dialog + div.modal-content + div.modal-header + button.close(type='button', data-dismiss='modal', aria-label='close') + span(aria-hidden='true') × + h4.modal-title Raw Card JSON + div.modal-body + pre.raw-json diff --git a/views/index.jade b/views/index.jade index 6d4e61f..1be5b51 100644 --- a/views/index.jade +++ b/views/index.jade @@ -84,15 +84,7 @@ block modals | Close button.btn.btn-primary(type='submit') | See Offers - div#card-json.modal.fade(tabindex='-1', role='dialog') - div.modal-dialog - div.modal-content - div.modal-header - button.close(type='button', data-dismiss='modal', aria-label='close') - span(aria-hidden='true') × - h4.modal-title Raw Card JSON - div.modal-body - pre.raw-json + include ./includes/raw-json-modal block scripts script. @@ -108,4 +100,4 @@ block scripts $('#card-json .raw-json').text(json); $('#card-json').modal('show'); }); - }); \ No newline at end of file + }); diff --git a/views/offers.jade b/views/offers.jade index 51ee65a..071253a 100644 --- a/views/offers.jade +++ b/views/offers.jade @@ -28,11 +28,12 @@ block content div.cards.container each card in products - div.card-info + div.card-info(data-raw-json="#{card.rawJSON}") div.main-info div.details p.card-title +cardNameLink(card) + a.json-toggle.pull-right(href="#", title="See raw JSON") {...} div.summary +cardNameImage(card) +marketingCopy(card) @@ -47,6 +48,9 @@ block content span.status-text Acknowledging... i.status-icon.fa.fa-circle-o +block modals + include ./includes/raw-json-modal + block scripts script. $(function() { @@ -100,3 +104,14 @@ block scripts $('#prequal-stats-summary .stat-text').html('FAILED to get prequalification summary data!') }) }) + + $(function () { + $('.card-info .json-toggle').click(function (evt) { + var card = $(this).closest('.card-info'), + json = JSON.stringify(card.data('rawJson'), null, 2); + evt.preventDefault(); + + $('#card-json .raw-json').text(json); + $('#card-json').modal('show'); + }); + }); From aaa9b094a04787027397b5fb3aeed5fa0f53a463 Mon Sep 17 00:00:00 2001 From: Ryan Korsak Date: Fri, 27 Jan 2017 13:01:35 -0500 Subject: [PATCH 106/147] Added SPDX statements to license headers --- app.js | 3 +++ creditoffers/client.js | 3 +++ creditoffers/index.js | 3 +++ creditoffers/prequalification.js | 3 +++ creditoffers/products.js | 3 +++ helpers/index.js | 3 +++ helpers/sanitize.js | 3 +++ oauth/index.js | 3 +++ public/css/style.css | 3 +++ routes/index.js | 3 +++ routes/offers.js | 3 +++ validation/customValidators.js | 3 +++ validation/customerInfo.js | 3 +++ validation/index.js | 3 +++ validation/stateCodes.js | 3 +++ viewmodels/index.js | 3 +++ viewmodels/preQualProduct.js | 3 +++ viewmodels/product.js | 3 +++ views/error.jade | 3 +++ views/includes/customer-form.jade | 3 +++ views/includes/raw-json-modal.jade | 3 +++ views/index.jade | 3 +++ views/layout.jade | 3 +++ views/offers.jade | 3 +++ 24 files changed, 72 insertions(+) diff --git a/app.js b/app.js index a15ef45..016e16e 100644 --- a/app.js +++ b/app.js @@ -11,6 +11,9 @@ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + +SPDX-Copyright: Copyright (c) Capital One Services, LLC +SPDX-License-Identifier: Apache-2.0 */ var express = require('express') diff --git a/creditoffers/client.js b/creditoffers/client.js index 4e1fc35..35f8663 100644 --- a/creditoffers/client.js +++ b/creditoffers/client.js @@ -11,6 +11,9 @@ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + +SPDX-Copyright: Copyright (c) Capital One Services, LLC +SPDX-License-Identifier: Apache-2.0 */ var request = require('request') diff --git a/creditoffers/index.js b/creditoffers/index.js index ccd6ae2..0321c6c 100644 --- a/creditoffers/index.js +++ b/creditoffers/index.js @@ -11,6 +11,9 @@ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + +SPDX-Copyright: Copyright (c) Capital One Services, LLC +SPDX-License-Identifier: Apache-2.0 */ var ApiClient = require('./client') diff --git a/creditoffers/prequalification.js b/creditoffers/prequalification.js index 9b6cb2b..b9fbe11 100644 --- a/creditoffers/prequalification.js +++ b/creditoffers/prequalification.js @@ -11,6 +11,9 @@ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + +SPDX-Copyright: Copyright (c) Capital One Services, LLC +SPDX-License-Identifier: Apache-2.0 */ var request = require('request') diff --git a/creditoffers/products.js b/creditoffers/products.js index 8e13ac6..dd62acf 100644 --- a/creditoffers/products.js +++ b/creditoffers/products.js @@ -11,6 +11,9 @@ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + +SPDX-Copyright: Copyright (c) Capital One Services, LLC +SPDX-License-Identifier: Apache-2.0 */ var request = require('request') diff --git a/helpers/index.js b/helpers/index.js index a9a4f41..397686a 100644 --- a/helpers/index.js +++ b/helpers/index.js @@ -11,6 +11,9 @@ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + +SPDX-Copyright: Copyright (c) Capital One Services, LLC +SPDX-License-Identifier: Apache-2.0 */ module.exports = { diff --git a/helpers/sanitize.js b/helpers/sanitize.js index 613de78..381ecff 100644 --- a/helpers/sanitize.js +++ b/helpers/sanitize.js @@ -11,6 +11,9 @@ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + +SPDX-Copyright: Copyright (c) Capital One Services, LLC +SPDX-License-Identifier: Apache-2.0 */ var sanitizeHtml = require('sanitize-html') diff --git a/oauth/index.js b/oauth/index.js index bb2ac67..aebe978 100644 --- a/oauth/index.js +++ b/oauth/index.js @@ -11,6 +11,9 @@ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + +SPDX-Copyright: Copyright (c) Capital One Services, LLC +SPDX-License-Identifier: Apache-2.0 */ var request = require('request') diff --git a/public/css/style.css b/public/css/style.css index 9662da7..6750407 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -11,6 +11,9 @@ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + +SPDX-Copyright: Copyright (c) Capital One Services, LLC +SPDX-License-Identifier: Apache-2.0 */ body { diff --git a/routes/index.js b/routes/index.js index 46613b0..ac16792 100644 --- a/routes/index.js +++ b/routes/index.js @@ -11,6 +11,9 @@ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + +SPDX-Copyright: Copyright (c) Capital One Services, LLC +SPDX-License-Identifier: Apache-2.0 */ var express = require('express') diff --git a/routes/offers.js b/routes/offers.js index 0073bec..310f4c0 100644 --- a/routes/offers.js +++ b/routes/offers.js @@ -11,6 +11,9 @@ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + +SPDX-Copyright: Copyright (c) Capital One Services, LLC +SPDX-License-Identifier: Apache-2.0 */ /* Defines routes related to finding and displaying credit offers */ diff --git a/validation/customValidators.js b/validation/customValidators.js index cc5b7bf..e18c801 100644 --- a/validation/customValidators.js +++ b/validation/customValidators.js @@ -11,6 +11,9 @@ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + +SPDX-Copyright: Copyright (c) Capital One Services, LLC +SPDX-License-Identifier: Apache-2.0 */ var stateCodes = require('./stateCodes') diff --git a/validation/customerInfo.js b/validation/customerInfo.js index 386541b..d8a1bf2 100644 --- a/validation/customerInfo.js +++ b/validation/customerInfo.js @@ -11,6 +11,9 @@ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + +SPDX-Copyright: Copyright (c) Capital One Services, LLC +SPDX-License-Identifier: Apache-2.0 */ /** @module Validation schema for incoming customer information **/ diff --git a/validation/index.js b/validation/index.js index 3bd6191..18a2a85 100644 --- a/validation/index.js +++ b/validation/index.js @@ -11,6 +11,9 @@ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + +SPDX-Copyright: Copyright (c) Capital One Services, LLC +SPDX-License-Identifier: Apache-2.0 */ module.exports = { diff --git a/validation/stateCodes.js b/validation/stateCodes.js index ca4bfb5..2d9b73f 100644 --- a/validation/stateCodes.js +++ b/validation/stateCodes.js @@ -11,6 +11,9 @@ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + +SPDX-Copyright: Copyright (c) Capital One Services, LLC +SPDX-License-Identifier: Apache-2.0 */ module.exports = [ diff --git a/viewmodels/index.js b/viewmodels/index.js index f92c362..b752a5b 100644 --- a/viewmodels/index.js +++ b/viewmodels/index.js @@ -11,6 +11,9 @@ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + +SPDX-Copyright: Copyright (c) Capital One Services, LLC +SPDX-License-Identifier: Apache-2.0 */ var product = require('./product') diff --git a/viewmodels/preQualProduct.js b/viewmodels/preQualProduct.js index 782209b..be1eee0 100644 --- a/viewmodels/preQualProduct.js +++ b/viewmodels/preQualProduct.js @@ -11,6 +11,9 @@ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + +SPDX-Copyright: Copyright (c) Capital One Services, LLC +SPDX-License-Identifier: Apache-2.0 */ /** @module Defines a consistent model for displaying pre-qualification products, which contain less info than normal products **/ diff --git a/viewmodels/product.js b/viewmodels/product.js index 9578186..d7a61d6 100644 --- a/viewmodels/product.js +++ b/viewmodels/product.js @@ -11,6 +11,9 @@ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + +SPDX-Copyright: Copyright (c) Capital One Services, LLC +SPDX-License-Identifier: Apache-2.0 */ /** @module Defines a consistent model for displaying products from the API **/ diff --git a/views/error.jade b/views/error.jade index 0077382..417c58a 100644 --- a/views/error.jade +++ b/views/error.jade @@ -10,6 +10,9 @@ //- distributed under the License is distributed on an "AS IS" BASIS, //- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. //- See the License for the specific language governing permissions and limitations under the License. +//- +//- SPDX-Copyright: Copyright (c) Capital One Services, LLC +//- SPDX-License-Identifier: Apache-2.0 extends ./layout.jade diff --git a/views/includes/customer-form.jade b/views/includes/customer-form.jade index df06157..37cb04e 100644 --- a/views/includes/customer-form.jade +++ b/views/includes/customer-form.jade @@ -10,6 +10,9 @@ //- distributed under the License is distributed on an "AS IS" BASIS, //- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. //- See the License for the specific language governing permissions and limitations under the License. +//- +//- SPDX-Copyright: Copyright (c) Capital One Services, LLC +//- SPDX-License-Identifier: Apache-2.0 //- A re-usable bootstrap text input mixin textinput(name, label, required) diff --git a/views/includes/raw-json-modal.jade b/views/includes/raw-json-modal.jade index f1f16f4..ad74957 100644 --- a/views/includes/raw-json-modal.jade +++ b/views/includes/raw-json-modal.jade @@ -10,6 +10,9 @@ //- distributed under the License is distributed on an "AS IS" BASIS, //- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. //- See the License for the specific language governing permissions and limitations under the License. +//- +//- SPDX-Copyright: Copyright (c) Capital One Services, LLC +//- SPDX-License-Identifier: Apache-2.0 div#card-json.modal.fade(tabindex='-1', role='dialog') div.modal-dialog diff --git a/views/index.jade b/views/index.jade index 1be5b51..5ec21b6 100644 --- a/views/index.jade +++ b/views/index.jade @@ -10,6 +10,9 @@ //- distributed under the License is distributed on an "AS IS" BASIS, //- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. //- See the License for the specific language governing permissions and limitations under the License. +//- +//- SPDX-Copyright: Copyright (c) Capital One Services, LLC +//- SPDX-License-Identifier: Apache-2.0 extends ./layout.jade diff --git a/views/layout.jade b/views/layout.jade index 3262fa4..25381fc 100644 --- a/views/layout.jade +++ b/views/layout.jade @@ -10,6 +10,9 @@ //- distributed under the License is distributed on an "AS IS" BASIS, //- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. //- See the License for the specific language governing permissions and limitations under the License. +//- +//- SPDX-Copyright: Copyright (c) Capital One Services, LLC +//- SPDX-License-Identifier: Apache-2.0 //- Common Card mixins mixin cardNameLink(card) diff --git a/views/offers.jade b/views/offers.jade index 071253a..b0e8837 100644 --- a/views/offers.jade +++ b/views/offers.jade @@ -10,6 +10,9 @@ //- distributed under the License is distributed on an "AS IS" BASIS, //- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. //- See the License for the specific language governing permissions and limitations under the License. +//- +//- SPDX-Copyright: Copyright (c) Capital One Services, LLC +//- SPDX-License-Identifier: Apache-2.0 extends ./layout From be224b67169656782a6de7477967ef66a2c05af0 Mon Sep 17 00:00:00 2001 From: Pamela Vong Date: Tue, 4 Apr 2017 12:05:11 -0400 Subject: [PATCH 107/147] Update all copyright years in source code to 2017 --- app.js | 2 +- bin/www | 2 +- config.js.sample | 2 +- creditoffers/client.js | 2 +- creditoffers/index.js | 2 +- creditoffers/prequalification.js | 2 +- creditoffers/products.js | 2 +- helpers/index.js | 2 +- helpers/sanitize.js | 2 +- oauth/index.js | 2 +- public/css/style.css | 2 +- routes/index.js | 2 +- routes/offers.js | 2 +- validation/customValidators.js | 2 +- validation/customerInfo.js | 2 +- validation/index.js | 2 +- validation/stateCodes.js | 2 +- viewmodels/index.js | 2 +- viewmodels/preQualProduct.js | 2 +- viewmodels/product.js | 2 +- views/error.jade | 2 +- views/includes/customer-form.jade | 2 +- views/includes/raw-json-modal.jade | 2 +- views/index.jade | 2 +- views/layout.jade | 2 +- views/offers.jade | 2 +- 26 files changed, 26 insertions(+), 26 deletions(-) diff --git a/app.js b/app.js index 016e16e..e8771f5 100644 --- a/app.js +++ b/app.js @@ -1,5 +1,5 @@ /* -Copyright 2016 Capital One Services, LLC +Copyright 2017 Capital One Services, LLC Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/bin/www b/bin/www index e0d23a8..d1e18df 100644 --- a/bin/www +++ b/bin/www @@ -1,6 +1,6 @@ #!/usr/bin/env node /* -Copyright 2016 Capital One Services, LLC +Copyright 2017 Capital One Services, LLC Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/config.js.sample b/config.js.sample index 385ba98..8cbb5d1 100644 --- a/config.js.sample +++ b/config.js.sample @@ -1,5 +1,5 @@ /* -Copyright 2016 Capital One Services, LLC +Copyright 2017 Capital One Services, LLC Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/creditoffers/client.js b/creditoffers/client.js index 35f8663..db9b8e3 100644 --- a/creditoffers/client.js +++ b/creditoffers/client.js @@ -1,5 +1,5 @@ /* -Copyright 2016 Capital One Services, LLC +Copyright 2017 Capital One Services, LLC Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/creditoffers/index.js b/creditoffers/index.js index 0321c6c..34806aa 100644 --- a/creditoffers/index.js +++ b/creditoffers/index.js @@ -1,5 +1,5 @@ /* -Copyright 2016 Capital One Services, LLC +Copyright 2017 Capital One Services, LLC Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/creditoffers/prequalification.js b/creditoffers/prequalification.js index b9fbe11..0d586ac 100644 --- a/creditoffers/prequalification.js +++ b/creditoffers/prequalification.js @@ -1,5 +1,5 @@ /* -Copyright 2016 Capital One Services, LLC +Copyright 2017 Capital One Services, LLC Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/creditoffers/products.js b/creditoffers/products.js index dd62acf..7666dc6 100644 --- a/creditoffers/products.js +++ b/creditoffers/products.js @@ -1,5 +1,5 @@ /* -Copyright 2016 Capital One Services, LLC +Copyright 2017 Capital One Services, LLC Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/helpers/index.js b/helpers/index.js index 397686a..188b30b 100644 --- a/helpers/index.js +++ b/helpers/index.js @@ -1,5 +1,5 @@ /* -Copyright 2016 Capital One Services, LLC +Copyright 2017 Capital One Services, LLC Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/helpers/sanitize.js b/helpers/sanitize.js index 381ecff..a901470 100644 --- a/helpers/sanitize.js +++ b/helpers/sanitize.js @@ -1,5 +1,5 @@ /* -Copyright 2016 Capital One Services, LLC +Copyright 2017 Capital One Services, LLC Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/oauth/index.js b/oauth/index.js index aebe978..1cd879b 100644 --- a/oauth/index.js +++ b/oauth/index.js @@ -1,5 +1,5 @@ /* -Copyright 2016 Capital One Services, LLC +Copyright 2017 Capital One Services, LLC Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/public/css/style.css b/public/css/style.css index 6750407..391367a 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -1,5 +1,5 @@ /* -Copyright 2016 Capital One Services, LLC +Copyright 2017 Capital One Services, LLC Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/routes/index.js b/routes/index.js index ac16792..496f633 100644 --- a/routes/index.js +++ b/routes/index.js @@ -1,5 +1,5 @@ /* -Copyright 2016 Capital One Services, LLC +Copyright 2017 Capital One Services, LLC Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/routes/offers.js b/routes/offers.js index 310f4c0..88baf6a 100644 --- a/routes/offers.js +++ b/routes/offers.js @@ -1,5 +1,5 @@ /* -Copyright 2016 Capital One Services, LLC +Copyright 2017 Capital One Services, LLC Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/validation/customValidators.js b/validation/customValidators.js index e18c801..35b3d29 100644 --- a/validation/customValidators.js +++ b/validation/customValidators.js @@ -1,5 +1,5 @@ /* -Copyright 2016 Capital One Services, LLC +Copyright 2017 Capital One Services, LLC Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/validation/customerInfo.js b/validation/customerInfo.js index d8a1bf2..236dc29 100644 --- a/validation/customerInfo.js +++ b/validation/customerInfo.js @@ -1,5 +1,5 @@ /* -Copyright 2016 Capital One Services, LLC +Copyright 2017 Capital One Services, LLC Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/validation/index.js b/validation/index.js index 18a2a85..ac016e3 100644 --- a/validation/index.js +++ b/validation/index.js @@ -1,5 +1,5 @@ /* -Copyright 2016 Capital One Services, LLC +Copyright 2017 Capital One Services, LLC Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/validation/stateCodes.js b/validation/stateCodes.js index 2d9b73f..b363a73 100644 --- a/validation/stateCodes.js +++ b/validation/stateCodes.js @@ -1,5 +1,5 @@ /* -Copyright 2016 Capital One Services, LLC +Copyright 2017 Capital One Services, LLC Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/viewmodels/index.js b/viewmodels/index.js index b752a5b..db64b45 100644 --- a/viewmodels/index.js +++ b/viewmodels/index.js @@ -1,5 +1,5 @@ /* -Copyright 2016 Capital One Services, LLC +Copyright 2017 Capital One Services, LLC Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/viewmodels/preQualProduct.js b/viewmodels/preQualProduct.js index be1eee0..be0277e 100644 --- a/viewmodels/preQualProduct.js +++ b/viewmodels/preQualProduct.js @@ -1,5 +1,5 @@ /* -Copyright 2016 Capital One Services, LLC +Copyright 2017 Capital One Services, LLC Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/viewmodels/product.js b/viewmodels/product.js index d7a61d6..383f0d7 100644 --- a/viewmodels/product.js +++ b/viewmodels/product.js @@ -1,5 +1,5 @@ /* -Copyright 2016 Capital One Services, LLC +Copyright 2017 Capital One Services, LLC Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/views/error.jade b/views/error.jade index 417c58a..734a492 100644 --- a/views/error.jade +++ b/views/error.jade @@ -1,4 +1,4 @@ -//- Copyright 2016 Capital One Services, LLC +//- Copyright 2017 Capital One Services, LLC //- //- Licensed under the Apache License, Version 2.0 (the "License"); //- you may not use this file except in compliance with the License. diff --git a/views/includes/customer-form.jade b/views/includes/customer-form.jade index 37cb04e..1b8320f 100644 --- a/views/includes/customer-form.jade +++ b/views/includes/customer-form.jade @@ -1,4 +1,4 @@ -//- Copyright 2016 Capital One Services, LLC +//- Copyright 2017 Capital One Services, LLC //- //- Licensed under the Apache License, Version 2.0 (the "License"); //- you may not use this file except in compliance with the License. diff --git a/views/includes/raw-json-modal.jade b/views/includes/raw-json-modal.jade index ad74957..c8138f1 100644 --- a/views/includes/raw-json-modal.jade +++ b/views/includes/raw-json-modal.jade @@ -1,4 +1,4 @@ -//- Copyright 2016 Capital One Services, LLC +//- Copyright 2017 Capital One Services, LLC //- //- Licensed under the Apache License, Version 2.0 (the "License"); //- you may not use this file except in compliance with the License. diff --git a/views/index.jade b/views/index.jade index 5ec21b6..6ce4634 100644 --- a/views/index.jade +++ b/views/index.jade @@ -1,4 +1,4 @@ -//- Copyright 2016 Capital One Services, LLC +//- Copyright 2017 Capital One Services, LLC //- //- Licensed under the Apache License, Version 2.0 (the "License"); //- you may not use this file except in compliance with the License. diff --git a/views/layout.jade b/views/layout.jade index 25381fc..79228e9 100644 --- a/views/layout.jade +++ b/views/layout.jade @@ -1,4 +1,4 @@ -//- Copyright 2016 Capital One Services, LLC +//- Copyright 2017 Capital One Services, LLC //- //- Licensed under the Apache License, Version 2.0 (the "License"); //- you may not use this file except in compliance with the License. diff --git a/views/offers.jade b/views/offers.jade index b0e8837..ee84dce 100644 --- a/views/offers.jade +++ b/views/offers.jade @@ -1,4 +1,4 @@ -//- Copyright 2016 Capital One Services, LLC +//- Copyright 2017 Capital One Services, LLC //- //- Licensed under the Apache License, Version 2.0 (the "License"); //- you may not use this file except in compliance with the License. From eaa7c7699ee7661aee04548ef4032e5bc8de4c29 Mon Sep 17 00:00:00 2001 From: Pamela Vong Date: Tue, 4 Apr 2017 14:14:30 -0400 Subject: [PATCH 108/147] Update product & preQual w/ latest from API models; Minor cleanup of unused mixins, properties --- viewmodels/preQualProduct.js | 13 +++++------ viewmodels/product.js | 42 ++++++++++++++++++++---------------- views/index.jade | 10 ++++----- views/layout.jade | 6 ------ views/offers.jade | 2 +- 5 files changed, 37 insertions(+), 36 deletions(-) diff --git a/viewmodels/preQualProduct.js b/viewmodels/preQualProduct.js index be0277e..d248298 100644 --- a/viewmodels/preQualProduct.js +++ b/viewmodels/preQualProduct.js @@ -24,6 +24,8 @@ var sanitize = require('../helpers').sanitize.sanitizeHtmlForDisplay module.exports = function preQualProduct (apiProduct) { var viewModel = _.pick(apiProduct, [ 'productId', + 'code', + 'priority', 'tier', 'terms', 'additionalInformationUrl' @@ -35,16 +37,15 @@ module.exports = function preQualProduct (apiProduct) { cardArt: _.find(apiProduct.images, { imageType: 'CardArt' }), banner: _.find(apiProduct.images, { imageType: 'Banner' }) } + // Normalize to the keys used by the products API - viewModel.primaryBenefitDescription = sanitize(_.get(apiProduct, 'terms.primaryBenefit')) - viewModel.purchaseAPRDescription = sanitize(_.get(apiProduct, 'terms.purchaseAprTerms')) - viewModel.balanceTransferAPRDescription = sanitize(_.get(apiProduct, 'terms.balanceTransferTerms')) - viewModel.annualMembershipFeeDescription = sanitize(_.get(apiProduct, 'terms.annualMembershipFeeTerms')) viewModel.applyNowLink = apiProduct.applicationUrl + var primaryBenefitDescription = sanitize(_.get(apiProduct, 'terms.primaryBenefit')) + var balanceTransferAPRDescription = sanitize(_.get(apiProduct, 'terms.balanceTransferTerms')) var marketingCopy = _.compact([ - viewModel.primaryBenefitDescription, - viewModel.balanceTransferAPRDescription + primaryBenefitDescription, + balanceTransferAPRDescription ]) viewModel.mainMarketingCopy = _.take(marketingCopy, 2) viewModel.extraMarketingCopy = _.drop(marketingCopy, 2) diff --git a/viewmodels/product.js b/viewmodels/product.js index 383f0d7..2ca0b43 100644 --- a/viewmodels/product.js +++ b/viewmodels/product.js @@ -29,26 +29,34 @@ module.exports = function product (apiProduct) { 'publishedDate', 'applyNowLink', 'productType', - 'brand', + 'brandName', 'categoryTags', - 'productKeywords', 'processingNetwork', 'creditRating', - 'rewardsType', - 'primaryBenefitDescription', - 'balanceTransferAPRDescription', - 'introPurchaseAPRDescription', - 'purchaseAPRDescription', - 'annualMembershipFeeDescription', - 'rewardsRateDescription', - 'foreignTransactionFeeDescription', - 'fraudCoverageDescription', - 'latePaymentDescription', - 'penaltyAPRDescription', - 'cashAdvanceFeeDescription', - 'cashAdvanceAPRDescription', + 'rewards', + 'balanceTransfer', + 'introBalanceTransferApr', + 'introBalanceTransferAprPeriod', + 'balanceTransferApr', + 'balanceTransferGracePeriod', + 'introPurchaseApr', + 'introPurchaseAprPeriod', + 'purchaseApr', + 'purchaseGracePeriod', + 'annualMembershipFee', + 'foreignTransactionFee', + 'fraudCoverage', + 'latePaymentFee', + 'penaltyApr', + 'cashAdvanceFee', + 'cashAdvanceApr', + 'cashAdvanceGracePeriod', 'generalDescription', - 'promotionalDescriptions' + 'promotionalCopy', + 'overLimitFee', + 'minimumDeposit', + 'additionalMarketingCopy', + 'productCount' ]) viewModel.productDisplayName = sanitize(apiProduct.productDisplayName || '???') @@ -58,8 +66,6 @@ module.exports = function product (apiProduct) { banner: _.find(apiProduct.images, { imageType: 'Banner' }) } - viewModel.additionalInformationUrl = _.get(apiProduct, 'links.self.href') - var marketingCopy = _.map(apiProduct.marketingCopy || [], sanitize) viewModel.mainMarketingCopy = _.take(marketingCopy, 2) viewModel.extraMarketingCopy = _.drop(marketingCopy, 2) diff --git a/views/index.jade b/views/index.jade index 6ce4634..fff5291 100644 --- a/views/index.jade +++ b/views/index.jade @@ -44,7 +44,7 @@ block content div.main-info div.details p.card-title - +cardNameLink(card) + !{card.productDisplayName} a.json-toggle.pull-right(href="#", title="See raw JSON") {...} div.summary +cardNameImage(card)(data-toggle='tooltip', title='#{(card.productKeywords || []).join(", ")}') @@ -56,16 +56,16 @@ block content div.additional-info div.info div.info-label Purchases Intro APR - div.info-value= card.introPurchaseAPRDescription || 'N/A' + div.info-value= card.introPurchaseApr.introPurchaseAprDescription || 'N/A' div.info div.info-label Balance Transfers Intro APR - div.info-value= card.balanceTransferAPRDescription || 'N/A' + div.info-value= card.introBalanceTransferApr.introBalanceTransferAprDescription || 'N/A' div.info div.info-label Regular APR - div.info-value= card.purchaseAPRDescription || 'N/A' + div.info-value= card.purchaseApr.purchaseAprDescription || 'N/A' div.info div.info-label Annual Fee - div.info-value= card.annualMembershipFeeDescription || 'N/A' + div.info-value= card.annualMembershipFee || 'N/A' div.info div.info-label Credit Needed div.info-value= (card.creditRating || []).join(', ' ) || 'N/A' diff --git a/views/layout.jade b/views/layout.jade index 79228e9..b28a41b 100644 --- a/views/layout.jade +++ b/views/layout.jade @@ -15,12 +15,6 @@ //- SPDX-License-Identifier: Apache-2.0 //- Common Card mixins -mixin cardNameLink(card) - if card.additionalInformationUrl - span - a(href=card.additionalInformationUrl) !{card.productDisplayName} - else - span !{card.productDisplayName} mixin cardNameImage(card) - var image = card.images && (card.images.cardArt || card.images.cardName || card.images.banner) diff --git a/views/offers.jade b/views/offers.jade index ee84dce..2398505 100644 --- a/views/offers.jade +++ b/views/offers.jade @@ -35,7 +35,7 @@ block content div.main-info div.details p.card-title - +cardNameLink(card) + !{card.productDisplayName} a.json-toggle.pull-right(href="#", title="See raw JSON") {...} div.summary +cardNameImage(card) From d380d038f1b90c8de49fdb15c090324394c0d47d Mon Sep 17 00:00:00 2001 From: Em Date: Wed, 12 Jul 2017 11:05:46 -0400 Subject: [PATCH 109/147] Update README.md updating path for config.sample --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b6e47ca..3b97837 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ All other dependencies are loaded with [npm](https://www.npmjs.com/). All depend ### config.js You'll need to set up your `config.js` file before you can run the app. -* Create this file by copying and renaming [config.js.sample](/credit-offers/config.js.sample). Be careful not to put `config.js` into version control. (We've added it to the repository's `.gitignore` for you.) +* Create this file by copying and renaming [config.js.sample](https://github.com/capitalone/CreditOffers-API-reference-app/blob/master/config.js.sample). Be careful not to put `config.js` into version control. (We've added it to the repository's `.gitignore` for you.) * Make sure that you've registered an app on [Capital One's developer portal](developer.capitalone.com). * Edit the `clientID` and `clientSecret` values in `config.js` to specify the **Client ID** and **Client Secret** that were provided when you registered the app. From bdf722730e2c862687ee9d4e59f0ed341f17495f Mon Sep 17 00:00:00 2001 From: Em Date: Wed, 12 Jul 2017 11:25:24 -0400 Subject: [PATCH 110/147] Update README.md Fixing the link to the developer portal. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3b97837..f453d98 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ All other dependencies are loaded with [npm](https://www.npmjs.com/). All depend You'll need to set up your `config.js` file before you can run the app. * Create this file by copying and renaming [config.js.sample](https://github.com/capitalone/CreditOffers-API-reference-app/blob/master/config.js.sample). Be careful not to put `config.js` into version control. (We've added it to the repository's `.gitignore` for you.) -* Make sure that you've registered an app on [Capital One's developer portal](developer.capitalone.com). +* Make sure that you've registered an app on [Capital One's developer portal](https://developer.capitalone.com/). * Edit the `clientID` and `clientSecret` values in `config.js` to specify the **Client ID** and **Client Secret** that were provided when you registered the app. ### Start the app From e772c83d63dcfc3902545bbca1f6d4c500cf1d90 Mon Sep 17 00:00:00 2001 From: Em Date: Thu, 13 Jul 2017 10:36:22 -0400 Subject: [PATCH 111/147] Update config.js.sample Connect to version 3 --- config.js.sample | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.js.sample b/config.js.sample index 8cbb5d1..5ef4f06 100644 --- a/config.js.sample +++ b/config.js.sample @@ -27,7 +27,7 @@ module.exports = { client: { // The URL of the Credit Offers environment you are connecting to. url: creditOffersHost, - apiVersion: 2 + apiVersion: 3 }, oauth: { tokenURL: oauthHost + '/oauth2/token', From 7007cdfb760b5642aa99b358ab09043e2da7cfad Mon Sep 17 00:00:00 2001 From: Agarwal Date: Fri, 14 Jul 2017 15:52:21 -0400 Subject: [PATCH 112/147] Updated apply now wont work in SB mode --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index f453d98..400c82b 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,7 @@ From the project root: Navigate to http://localhost:3000. This will retrieve a list of Consumer card products from the API and display simple information about each. From here, you can try a few simple things: * Toggle the card type to 'Business' to request and display a list of business card products from the API + * In sandbox mode, "Apply now" button will not work * Click on the 'Find Pre-Qualified Offers' button to launch a simple customer information form and test out the pre-qualification API behavior. The results screen will also perform two asynchronous calls: * POST to `/credit-offers/prequalifications/{prequalificationId}` to acknowledge that the results were displayed to the customer * GET from the `/credit-offers/prequalifications-summary` endpoint to display simple pre-qualification statistics at the bottom of the page From 83e3737b847f8bd652d03632c18c5b98abca501c Mon Sep 17 00:00:00 2001 From: Em Date: Fri, 14 Jul 2017 16:16:47 -0400 Subject: [PATCH 113/147] Update README.md Added comment that Apply Now button will not work in Sandbox mode --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index f453d98..8dd9f5b 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,7 @@ From the project root: Navigate to http://localhost:3000. This will retrieve a list of Consumer card products from the API and display simple information about each. From here, you can try a few simple things: + * In sandbox mode, “Apply now” button will not work * Toggle the card type to 'Business' to request and display a list of business card products from the API * Click on the 'Find Pre-Qualified Offers' button to launch a simple customer information form and test out the pre-qualification API behavior. The results screen will also perform two asynchronous calls: * POST to `/credit-offers/prequalifications/{prequalificationId}` to acknowledge that the results were displayed to the customer From 7265f7c69498588f8ed473d680f24a00e965573c Mon Sep 17 00:00:00 2001 From: Em Date: Fri, 14 Jul 2017 16:18:58 -0400 Subject: [PATCH 114/147] Update preQualProduct.js --- viewmodels/preQualProduct.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/viewmodels/preQualProduct.js b/viewmodels/preQualProduct.js index d248298..64d469a 100644 --- a/viewmodels/preQualProduct.js +++ b/viewmodels/preQualProduct.js @@ -31,7 +31,7 @@ module.exports = function preQualProduct (apiProduct) { 'additionalInformationUrl' ]) - viewModel.productDisplayName = sanitize(apiProduct.productName || '???') + viewModel.productDisplayName = sanitize(apiProduct.productDisplayName || '???') viewModel.images = { cardName: _.find(apiProduct.images, { imageType: 'CardName' }), cardArt: _.find(apiProduct.images, { imageType: 'CardArt' }), From e481a05c8889b5e414b72dd9a40ae209e8f2299e Mon Sep 17 00:00:00 2001 From: Em Date: Mon, 17 Jul 2017 11:36:15 -0400 Subject: [PATCH 115/147] COFFERS-459 update reference app w v3 changes --- routes/offers.js | 2 ++ viewmodels/preQualProduct.js | 12 ++++-------- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/routes/offers.js b/routes/offers.js index 88baf6a..38e44b1 100644 --- a/routes/offers.js +++ b/routes/offers.js @@ -65,6 +65,8 @@ module.exports = function (client) { client.prequalification.create(customerInfo, function (err, response) { if (err) { return next(err) } + debug(response.prequalification) + var apiProducts = response.products || [] var productViewModels = _(apiProducts) .sortBy('priority') // Display in the priority order given by the API diff --git a/viewmodels/preQualProduct.js b/viewmodels/preQualProduct.js index d248298..abb355e 100644 --- a/viewmodels/preQualProduct.js +++ b/viewmodels/preQualProduct.js @@ -20,6 +20,7 @@ SPDX-License-Identifier: Apache-2.0 var _ = require('lodash') var sanitize = require('../helpers').sanitize.sanitizeHtmlForDisplay +var debug = require('debug')('name') module.exports = function preQualProduct (apiProduct) { var viewModel = _.pick(apiProduct, [ @@ -31,7 +32,7 @@ module.exports = function preQualProduct (apiProduct) { 'additionalInformationUrl' ]) - viewModel.productDisplayName = sanitize(apiProduct.productName || '???') + viewModel.productDisplayName = sanitize(apiProduct.productDisplayName || '???') viewModel.images = { cardName: _.find(apiProduct.images, { imageType: 'CardName' }), cardArt: _.find(apiProduct.images, { imageType: 'CardArt' }), @@ -39,14 +40,9 @@ module.exports = function preQualProduct (apiProduct) { } // Normalize to the keys used by the products API - viewModel.applyNowLink = apiProduct.applicationUrl + viewModel.applyNowLink = apiProduct.applyNowLink - var primaryBenefitDescription = sanitize(_.get(apiProduct, 'terms.primaryBenefit')) - var balanceTransferAPRDescription = sanitize(_.get(apiProduct, 'terms.balanceTransferTerms')) - var marketingCopy = _.compact([ - primaryBenefitDescription, - balanceTransferAPRDescription - ]) + var marketingCopy = _.map(apiProduct.marketingCopy || [], sanitize) viewModel.mainMarketingCopy = _.take(marketingCopy, 2) viewModel.extraMarketingCopy = _.drop(marketingCopy, 2) From 0ff3e78bee361622dfd7178239fc47adcee2e9bd Mon Sep 17 00:00:00 2001 From: Em Date: Mon, 17 Jul 2017 11:42:44 -0400 Subject: [PATCH 116/147] Removing debugging statements --- routes/offers.js | 2 -- viewmodels/preQualProduct.js | 1 - 2 files changed, 3 deletions(-) diff --git a/routes/offers.js b/routes/offers.js index 38e44b1..88baf6a 100644 --- a/routes/offers.js +++ b/routes/offers.js @@ -65,8 +65,6 @@ module.exports = function (client) { client.prequalification.create(customerInfo, function (err, response) { if (err) { return next(err) } - debug(response.prequalification) - var apiProducts = response.products || [] var productViewModels = _(apiProducts) .sortBy('priority') // Display in the priority order given by the API diff --git a/viewmodels/preQualProduct.js b/viewmodels/preQualProduct.js index abb355e..832c89d 100644 --- a/viewmodels/preQualProduct.js +++ b/viewmodels/preQualProduct.js @@ -20,7 +20,6 @@ SPDX-License-Identifier: Apache-2.0 var _ = require('lodash') var sanitize = require('../helpers').sanitize.sanitizeHtmlForDisplay -var debug = require('debug')('name') module.exports = function preQualProduct (apiProduct) { var viewModel = _.pick(apiProduct, [ From 12f9972858898d17debb4796c4a21cad1004208e Mon Sep 17 00:00:00 2001 From: neetiagarwal Date: Mon, 17 Jul 2017 12:04:26 -0400 Subject: [PATCH 117/147] Changes api version to v3 --- creditoffers/client.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/creditoffers/client.js b/creditoffers/client.js index db9b8e3..b20b2e4 100644 --- a/creditoffers/client.js +++ b/creditoffers/client.js @@ -25,7 +25,7 @@ var util = require('util') // Default to a secure call to the API endpoint var defaultOptions = { url: 'https://api.capitalone.com', - apiVersion: 2 + apiVersion: 3 } /** From 08eff7978ba7b3c01442f2c579d0dc1b4af2e4bf Mon Sep 17 00:00:00 2001 From: Em Date: Mon, 17 Jul 2017 14:41:59 -0400 Subject: [PATCH 118/147] Added APR information on prequalification and disabled Apply Now link on SB --- package-lock.json | 1230 ++++++++++++++++++++++++++++++++++ viewmodels/preQualProduct.js | 7 +- views/index.jade | 17 +- views/layout.jade | 17 +- views/offers.jade | 29 +- 5 files changed, 1269 insertions(+), 31 deletions(-) create mode 100644 package-lock.json diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..2ae1733 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1230 @@ +{ + "name": "credit-offers", + "version": "2.0.0", + "lockfileVersion": 1, + "dependencies": { + "accepts": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.2.13.tgz", + "integrity": "sha1-5fHzkoxtlf2WVYw27D2dDeSm7Oo=" + }, + "acorn": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-2.7.0.tgz", + "integrity": "sha1-q259nYhqrKiwhbwzEreaGYQz8Oc=" + }, + "acorn-globals": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-1.0.9.tgz", + "integrity": "sha1-VbtemGkVB7dFedBRNBMhfDgMVM8=" + }, + "align-text": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", + "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=" + }, + "amdefine": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", + "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=" + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + }, + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + }, + "asap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/asap/-/asap-1.0.0.tgz", + "integrity": "sha1-sqRdpf36ILBJb8N2jMJ8EvqRan0=" + }, + "asn1": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", + "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=" + }, + "assert-plus": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz", + "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=" + }, + "async": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/async/-/async-2.5.0.tgz", + "integrity": "sha512-e+lJAJeNWuPCNyxZKOBdaJGyLGHugXVQtrAwtuAe2vhxTYxFTKE73p8JuTmdH0qdQZtDvI4dhJwjZc5zsfIsYw==", + "dependencies": { + "lodash": { + "version": "4.17.4", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", + "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=" + } + } + }, + "aws-sign2": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz", + "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=" + }, + "aws4": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", + "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=" + }, + "basic-auth": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-1.0.4.tgz", + "integrity": "sha1-Awk1sB3nyblKgksp8/zLdQ06UpA=" + }, + "bcrypt-pbkdf": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", + "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", + "optional": true + }, + "bl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bl/-/bl-1.0.3.tgz", + "integrity": "sha1-/FQhoo/UImA2w7OJGmaiW8ZNIm4=" + }, + "bluebird": { + "version": "3.4.7", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz", + "integrity": "sha1-9y12C+Cbf3bQjtj66Ysomo0F+rM=" + }, + "body-parser": { + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.13.3.tgz", + "integrity": "sha1-wIzzMMM1jhUQFqBXRvE/ApyX+pc=" + }, + "boom": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", + "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=" + }, + "bootstrap": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-3.3.7.tgz", + "integrity": "sha1-WjiTlFSfIzMIdaOxUGVldPip63E=" + }, + "bytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-2.1.0.tgz", + "integrity": "sha1-rJPEEOL/ycx89LRks4KJBn9eR7Q=" + }, + "camelcase": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", + "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=" + }, + "camelize": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.0.tgz", + "integrity": "sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs=" + }, + "caseless": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz", + "integrity": "sha1-cVuW6phBWTzDMGeSP17GDr2k99c=" + }, + "center-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", + "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=" + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=" + }, + "character-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/character-parser/-/character-parser-1.2.1.tgz", + "integrity": "sha1-wN3kqxgnE7kZuXCVmhI+zBow/NY=" + }, + "clean-css": { + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-3.4.27.tgz", + "integrity": "sha1-re91sxwWD/pdcvTeZ5ZuJmDBolU=", + "dependencies": { + "commander": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.8.1.tgz", + "integrity": "sha1-Br42f+v9oMMwqh4qBy09yXYkJdQ=" + } + } + }, + "cliui": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", + "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", + "dependencies": { + "wordwrap": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", + "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=" + } + } + }, + "combined-stream": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", + "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=" + }, + "commander": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.6.0.tgz", + "integrity": "sha1-nfflL7Kgyw+4kFjugMMQQiXzfh0=" + }, + "connect": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.4.0.tgz", + "integrity": "sha1-7oeJo71GBL/aOdvPHTu0gt/mzyQ=", + "dependencies": { + "escape-html": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.2.tgz", + "integrity": "sha1-130y+pjjjC9BroXpJ44ODmuhAiw=" + }, + "finalhandler": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-0.4.0.tgz", + "integrity": "sha1-llpS2ejQXSuFdUhUH7ibU6JJfZs=" + } + } + }, + "constantinople": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/constantinople/-/constantinople-3.0.2.tgz", + "integrity": "sha1-S5RdmTeQe82Y7ldRIsOBdRZUQUE=" + }, + "content-disposition": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.1.tgz", + "integrity": "sha1-h0dsamfI2qh+Muh2Ft+IO6f7Bxs=" + }, + "content-security-policy-builder": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/content-security-policy-builder/-/content-security-policy-builder-1.0.0.tgz", + "integrity": "sha1-Ef1AxcwpimxyWjX5rPcegqtdMkM=" + }, + "content-type": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.2.tgz", + "integrity": "sha1-t9ETrueo3Se9IRM8TcJSnfFyHu0=" + }, + "cookie": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.1.3.tgz", + "integrity": "sha1-5zSlwUF/zkctWu+Cw4HKu2TRpDU=" + }, + "cookie-parser": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.3.5.tgz", + "integrity": "sha1-nXVVcPtdF4kHcSJ6AjFNm+fPg1Y=" + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "cryptiles": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", + "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=" + }, + "csrf": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/csrf/-/csrf-3.0.6.tgz", + "integrity": "sha1-thEg3c7q/JHnbtUxO7XAsmZ7cQo=" + }, + "css": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/css/-/css-1.0.8.tgz", + "integrity": "sha1-k4aBHKgrzMnuf7WnMrHioxfIo+c=" + }, + "css-parse": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/css-parse/-/css-parse-1.0.4.tgz", + "integrity": "sha1-OLBQP7+dqfVOnB29pg4UXHcRe90=" + }, + "css-stringify": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/css-stringify/-/css-stringify-1.0.5.tgz", + "integrity": "sha1-sNBClG2ylTu50pKQCmy19tASIDE=" + }, + "csurf": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/csurf/-/csurf-1.8.3.tgz", + "integrity": "sha1-I/KhO/HY/OHQyZZYg5RELLqGpWo=" + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + } + } + }, + "dashify": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/dashify/-/dashify-0.2.2.tgz", + "integrity": "sha1-agdBWgHJH69KMuONnfunH2HLIP4=" + }, + "debug": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=" + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, + "depd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.0.1.tgz", + "integrity": "sha1-gK7GTJ1tl+ZcwqnKqTwKpqv3Oqo=" + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + }, + "dns-prefetch-control": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/dns-prefetch-control/-/dns-prefetch-control-0.1.0.tgz", + "integrity": "sha1-YN20V3dOF48flBXwyrsOhbCzALI=" + }, + "dom-serializer": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz", + "integrity": "sha1-BzxpdUbOB4DOI75KKOKT5AvDDII=", + "dependencies": { + "domelementtype": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz", + "integrity": "sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs=" + } + } + }, + "domelementtype": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.0.tgz", + "integrity": "sha1-sXrtguirWeUt2cGbF1bg/BhyBMI=" + }, + "domhandler": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.1.tgz", + "integrity": "sha1-iS5HAAqZvlW783dP/qBWHYh5wlk=" + }, + "domutils": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.6.2.tgz", + "integrity": "sha1-GVjMC0yUJuntNn+xyOhUiRsPo/8=" + }, + "dont-sniff-mimetype": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dont-sniff-mimetype/-/dont-sniff-mimetype-1.0.0.tgz", + "integrity": "sha1-WTKJDcn04vGeXrAqIAJuXl78j1g=" + }, + "ecc-jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", + "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", + "optional": true + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "ejs": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.3.4.tgz", + "integrity": "sha1-PHbKoJZks1g7ADevncE2557Gi5g=" + }, + "entities": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.1.tgz", + "integrity": "sha1-blwtClYhtdra7O+AuQ7ftc13cvA=" + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + }, + "etag": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.7.0.tgz", + "integrity": "sha1-A9MLX2fdbmMtKUXTDWZScxo01dg=" + }, + "express": { + "version": "4.13.4", + "resolved": "https://registry.npmjs.org/express/-/express-4.13.4.tgz", + "integrity": "sha1-PAt288d1kMg0VzkGHsC9O6Bn7CQ=", + "dependencies": { + "cookie": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.1.5.tgz", + "integrity": "sha1-armUiksa4hlSzSWIUwpHItQETXw=" + }, + "depd": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.0.tgz", + "integrity": "sha1-4b2Cxqq2ztlluXuIsX7T5SjKGMM=" + } + } + }, + "express-validator": { + "version": "2.21.0", + "resolved": "https://registry.npmjs.org/express-validator/-/express-validator-2.21.0.tgz", + "integrity": "sha1-9fwvn6npqFeGNPEOhrpaQ0a5b08=", + "dependencies": { + "lodash": { + "version": "4.16.6", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.16.6.tgz", + "integrity": "sha1-0iyaxmAojzhD4Wun0rXQbMon13c=" + } + } + }, + "extend": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", + "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=" + }, + "extsprintf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.0.2.tgz", + "integrity": "sha1-4QgOBljjALBilJkMxw4VAiNf1VA=" + }, + "finalhandler": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-0.4.1.tgz", + "integrity": "sha1-haF8bFmpRxfSYtYSMNSw6+PUoU0=" + }, + "font-awesome": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/font-awesome/-/font-awesome-4.7.0.tgz", + "integrity": "sha1-j6jPBBGhoxr9B7BtKQK7n8gVoTM=" + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + }, + "form-data": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-1.0.1.tgz", + "integrity": "sha1-rjFduaSQf6BlUCMEpm13M0de43w=" + }, + "forwarded": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.0.tgz", + "integrity": "sha1-Ge+YdMSuHCl7zweP3mOgm2aoQ2M=" + }, + "frameguard": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/frameguard/-/frameguard-1.0.0.tgz", + "integrity": "sha1-PMesF07JCSG712mQgKjyIfXAADk=" + }, + "fresh": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.3.0.tgz", + "integrity": "sha1-ZR+DjiJCTnVm3hYdg1jKoZn4PU8=" + }, + "generate-function": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz", + "integrity": "sha1-aFj+fAlpt9TpCTM3ZHrHn2DfvnQ=" + }, + "generate-object-property": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz", + "integrity": "sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=" + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + } + } + }, + "graceful-readlink": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", + "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=" + }, + "har-validator": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.6.tgz", + "integrity": "sha1-zcvAgYgmWtEZtqWnyKtw7s+10n0=", + "dependencies": { + "commander": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz", + "integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==" + } + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=" + }, + "hawk": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", + "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=" + }, + "helmet": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/helmet/-/helmet-1.1.0.tgz", + "integrity": "sha1-HdWea0Vo/b5Ku9tgy0F06wM4KaY=" + }, + "helmet-csp": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/helmet-csp/-/helmet-csp-1.0.3.tgz", + "integrity": "sha1-FNBkHs81IK6fc3vKE4RKf09kHXI=" + }, + "hide-powered-by": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/hide-powered-by/-/hide-powered-by-1.0.0.tgz", + "integrity": "sha1-SoWtZYgfYoV/xwr3F0oRhNzM4ys=" + }, + "hoek": { + "version": "2.16.3", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", + "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=" + }, + "hpkp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/hpkp/-/hpkp-1.0.0.tgz", + "integrity": "sha1-hIPkqarIBVtgPkK5AiIaB4y/VfE=" + }, + "hsts": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/hsts/-/hsts-1.0.0.tgz", + "integrity": "sha1-mOEDnverpVQFe2sOMlhMCxFDpBQ=" + }, + "html-entities": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.2.0.tgz", + "integrity": "sha1-QZSMr4XOgv7Tbk5qDtNxpmZDeeI=" + }, + "htmlparser2": { + "version": "3.9.2", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.9.2.tgz", + "integrity": "sha1-G9+HrMoPP55T+k/M6w9LTLsAszg=" + }, + "http-errors": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.3.1.tgz", + "integrity": "sha1-GX4izevUGYWF6GlO9nhhl7ke2UI=" + }, + "http-signature": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", + "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=" + }, + "iconv-lite": { + "version": "0.4.11", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.11.tgz", + "integrity": "sha1-LstC/SlHRJIiCaLnxATayHk9it4=" + }, + "ienoopen": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ienoopen/-/ienoopen-1.0.0.tgz", + "integrity": "sha1-NGpCj0dKrI9QzzeE6i0PFvYr2ms=" + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "ipaddr.js": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.0.5.tgz", + "integrity": "sha1-X6eM8wG4JceKvDBC2BJyMEnqI8c=" + }, + "is-buffer": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.5.tgz", + "integrity": "sha1-Hzsm72E7IUuIy8ojzGwB2Hlh7sw=" + }, + "is-my-json-valid": { + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.16.0.tgz", + "integrity": "sha1-8Hndm/2uZe4gOKrorLyGqxCeNpM=" + }, + "is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", + "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=" + }, + "is-property": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=" + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, + "jade": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/jade/-/jade-1.11.0.tgz", + "integrity": "sha1-nIDlOMEtP7lcjZu5VZ+gzAQEBf0=" + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "optional": true + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, + "jsonpointer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz", + "integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk=" + }, + "jsprim": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.0.tgz", + "integrity": "sha1-o7h+QCmNjDgFUtjMdiigu5WiKRg=", + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + } + } + }, + "jstransformer": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/jstransformer/-/jstransformer-0.0.2.tgz", + "integrity": "sha1-eq4pqQPRls+glz2IXT5HlH7Ndqs=" + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=" + }, + "lazy-cache": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", + "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=" + }, + "lodash": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.1.0.tgz", + "integrity": "sha1-KZiUKD3gGp7vvt/0xLmwCmoubpY=" + }, + "lodash._baseassign": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz", + "integrity": "sha1-jDigmVAPIVrQnlnxci/QxSv+Ck4=" + }, + "lodash._basecallback": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/lodash._basecallback/-/lodash._basecallback-3.3.1.tgz", + "integrity": "sha1-t7K7Q9whYEJKIczybFfkQ3cqjic=" + }, + "lodash._basecopy": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", + "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=" + }, + "lodash._baseeach": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/lodash._baseeach/-/lodash._baseeach-3.0.4.tgz", + "integrity": "sha1-z4cGVyyhROjZ11InyZDamC+TKvM=" + }, + "lodash._baseisequal": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/lodash._baseisequal/-/lodash._baseisequal-3.0.7.tgz", + "integrity": "sha1-2AJfdjOdKTQnZ9zIh85cuVpbUfE=" + }, + "lodash._basereduce": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/lodash._basereduce/-/lodash._basereduce-3.0.2.tgz", + "integrity": "sha1-E/uY+94WIIOgyWfwYFwyrPuycLI=" + }, + "lodash._bindcallback": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz", + "integrity": "sha1-5THCdkTPi1epnhftlbNcdIeJOS4=" + }, + "lodash._createassigner": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lodash._createassigner/-/lodash._createassigner-3.1.1.tgz", + "integrity": "sha1-g4pbri/aymOsIt7o4Z+k5taXCxE=" + }, + "lodash._getnative": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", + "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=" + }, + "lodash._isiterateecall": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", + "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=" + }, + "lodash.assign": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-3.2.0.tgz", + "integrity": "sha1-POnwI0tLIiPilrj6CsH+6OvKZPo=" + }, + "lodash.isarguments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", + "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=" + }, + "lodash.isarray": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", + "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=" + }, + "lodash.isfunction": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/lodash.isfunction/-/lodash.isfunction-3.0.6.tgz", + "integrity": "sha1-M+WojjtEQw7elx60Pa8VntHd45U=" + }, + "lodash.isstring": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-3.0.1.tgz", + "integrity": "sha1-QWOJROoELvZ61nwpOqVB0/PW5Tw=" + }, + "lodash.istypedarray": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/lodash.istypedarray/-/lodash.istypedarray-3.0.6.tgz", + "integrity": "sha1-yaR3SYYHUB2OhJTSg7h8OSgc72I=" + }, + "lodash.keys": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", + "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=" + }, + "lodash.pairs": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash.pairs/-/lodash.pairs-3.0.1.tgz", + "integrity": "sha1-u+CNV4bu6qCaFckevw3LfSvjJqk=" + }, + "lodash.reduce": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lodash.reduce/-/lodash.reduce-3.1.2.tgz", + "integrity": "sha1-KvPixoig2TnYbqcXFOjNU84yewI=" + }, + "lodash.restparam": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz", + "integrity": "sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=" + }, + "lodash.some": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/lodash.some/-/lodash.some-3.2.3.tgz", + "integrity": "sha1-djN9pP4E8NMvEMk3ha8PYFv+Lq8=" + }, + "longest": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", + "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=" + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" + }, + "mime": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.3.4.tgz", + "integrity": "sha1-EV+eO2s9rylZmDyzjxSaLUDrXVM=" + }, + "mime-db": { + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.27.0.tgz", + "integrity": "sha1-gg9XIpa70g7CXtVeW13oaeVDbrE=" + }, + "mime-types": { + "version": "2.1.15", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.15.tgz", + "integrity": "sha1-pOv1BkCUVpI3uM9wBGd20J/JKu0=" + }, + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=" + }, + "moment": { + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.16.0.tgz", + "integrity": "sha1-848sl8mImw7hj8bMOS4eRDrS2o4=" + }, + "morgan": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.6.1.tgz", + "integrity": "sha1-X9gYOYxoGcuiinzWZk8pL+HAu/I=" + }, + "ms": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", + "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=" + }, + "negotiator": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.5.3.tgz", + "integrity": "sha1-Jp1cR2gQ7JLtvntsLygxY4T5p+g=" + }, + "nocache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/nocache/-/nocache-1.0.0.tgz", + "integrity": "sha1-MgZe+F9uYqAUVCwrK68RuzcE3yE=" + }, + "node-uuid": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz", + "integrity": "sha1-sEDrCSOWivq/jTL7HxfxFn/auQc=" + }, + "oauth-sign": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", + "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=" + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=" + }, + "on-headers": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.1.tgz", + "integrity": "sha1-ko9dD0cNSTQmUepnlLCFfBAGk/c=" + }, + "optimist": { + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.3.7.tgz", + "integrity": "sha1-yQlBrVnkJzMokjB00s8ufLxuwNk=" + }, + "parseurl": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.1.tgz", + "integrity": "sha1-yKuMkiO6NIiKpkopeyiFO+wY2lY=" + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=" + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=" + }, + "platform": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/platform/-/platform-1.3.0.tgz", + "integrity": "sha1-VrTFp0jyCjyBXIMWj8oo+4u2+9Q=" + }, + "process-nextick-args": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", + "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=" + }, + "promise": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/promise/-/promise-6.1.0.tgz", + "integrity": "sha1-LOcp9rlLRcJoka0GAsXJDgTG7vY=" + }, + "proxy-addr": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-1.0.10.tgz", + "integrity": "sha1-DUCoL4Afw1VWfS7LZe/j8HfxIcU=" + }, + "qs": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-4.0.0.tgz", + "integrity": "sha1-wx2bdOwn33XlQ6hseHKO2NRiNgc=" + }, + "random-bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", + "integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs=" + }, + "range-parser": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.0.3.tgz", + "integrity": "sha1-aHKCNTXGkuLCoBA4Jq/YLC4P8XU=" + }, + "raw-body": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.1.7.tgz", + "integrity": "sha1-rf6s4uT7MJgFgBTQjActzFl1h3Q=", + "dependencies": { + "bytes": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-2.4.0.tgz", + "integrity": "sha1-fZcZb51br39pNeJZhVSe3SpsIzk=" + }, + "iconv-lite": { + "version": "0.4.13", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.13.tgz", + "integrity": "sha1-H4irpKsLFQjoMSrMOTRfNumS4vI=" + } + } + }, + "readable-stream": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", + "integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=" + }, + "regexp-quote": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/regexp-quote/-/regexp-quote-0.0.0.tgz", + "integrity": "sha1-Hg9GUMhi3L/tVP1CsUjpuxch/PI=" + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=" + }, + "request": { + "version": "2.69.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.69.0.tgz", + "integrity": "sha1-z5HS4AB1KxIXFVwAUkGRGZGiNGo=", + "dependencies": { + "qs": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.0.4.tgz", + "integrity": "sha1-UQGdhHIMk5uCc36EVWp4Izjs6ns=" + } + } + }, + "right-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", + "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=" + }, + "rndm": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/rndm/-/rndm-1.2.0.tgz", + "integrity": "sha1-8z/pz7Urv9UgqhgyO8ZdsRCht2w=" + }, + "sanitize-html": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-1.13.0.tgz", + "integrity": "sha1-TuF8vsUWv+MvLOZoaladfmtPNjE=" + }, + "send": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/send/-/send-0.13.1.tgz", + "integrity": "sha1-ow1fTILIqbrprQCh2bG9vm8Zntc=", + "dependencies": { + "depd": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.0.tgz", + "integrity": "sha1-4b2Cxqq2ztlluXuIsX7T5SjKGMM=" + }, + "statuses": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.2.1.tgz", + "integrity": "sha1-3e1FzBglbVHtQK7BQkidXGECbSg=" + } + } + }, + "serve-favicon": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/serve-favicon/-/serve-favicon-2.3.0.tgz", + "integrity": "sha1-rtNsxoNAaabxicxyIsahqBHcWzk=" + }, + "serve-static": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.10.3.tgz", + "integrity": "sha1-zlpuzTEB/tXsCYJ9rCKpwpv7BTU=", + "dependencies": { + "depd": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.0.tgz", + "integrity": "sha1-4b2Cxqq2ztlluXuIsX7T5SjKGMM=" + }, + "send": { + "version": "0.13.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.13.2.tgz", + "integrity": "sha1-dl52B8gFVFK7pvCwUllTUJhgNt4=" + }, + "statuses": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.2.1.tgz", + "integrity": "sha1-3e1FzBglbVHtQK7BQkidXGECbSg=" + } + } + }, + "sntp": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", + "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=" + }, + "source-map": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", + "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=" + }, + "sshpk": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz", + "integrity": "sha1-US322mKHFEMW3EwY/hzx2UBzm+M=", + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + } + } + }, + "statuses": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", + "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=" + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + }, + "stringstream": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", + "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=" + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=" + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + }, + "tough-cookie": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.2.2.tgz", + "integrity": "sha1-yDoYMPTl7wuT7yo0iOck+N4Basc=" + }, + "transformers": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/transformers/-/transformers-2.1.0.tgz", + "integrity": "sha1-XSPLNVYd2F3Gf7hIIwm0fVPM6ac=", + "dependencies": { + "is-promise": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-1.0.1.tgz", + "integrity": "sha1-MVc3YcBX4zwukaq56W2gjO++duU=" + }, + "promise": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/promise/-/promise-2.0.0.tgz", + "integrity": "sha1-RmSKqdYFr10ucMMCS/WUNtoCuA4=" + }, + "source-map": { + "version": "0.1.43", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz", + "integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=" + }, + "uglify-js": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.2.5.tgz", + "integrity": "sha1-puAqcNg5eSuXgEiLe4sYTAlcmcc=" + } + } + }, + "tsscmp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.5.tgz", + "integrity": "sha1-fcSjOvcVgatDN9qR2FylQn69mpc=" + }, + "tunnel-agent": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz", + "integrity": "sha1-Y3PbdpCf5XDgjXNYM2Xtgop07us=" + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "optional": true + }, + "type-is": { + "version": "1.6.15", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.15.tgz", + "integrity": "sha1-yrEPtJCeRByChC6v4a1kbIGARBA=" + }, + "uglify-js": { + "version": "2.8.29", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", + "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", + "dependencies": { + "source-map": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", + "integrity": "sha1-dc449SvwczxafwwRjYEzSiu19BI=" + } + } + }, + "uglify-to-browserify": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", + "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", + "optional": true + }, + "uid-safe": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.4.tgz", + "integrity": "sha1-Otbzg2jG1MjHXsF2I/t5qh0HHYE=" + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "utils-merge": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.0.tgz", + "integrity": "sha1-ApT7kiu5N1FTVBxPcJYjHyh8ivg=" + }, + "validator": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-5.7.0.tgz", + "integrity": "sha1-eoelgUa2laxIYHEUHAxJ1n2gXlw=" + }, + "vary": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.0.1.tgz", + "integrity": "sha1-meSYFWaihhGN+yuBc1ffeZM3bRA=" + }, + "verror": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.3.6.tgz", + "integrity": "sha1-z/XfEpRtKX0rqu+qJoniW+AcAFw=" + }, + "void-elements": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", + "integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=" + }, + "window-size": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", + "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=" + }, + "with": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/with/-/with-4.0.3.tgz", + "integrity": "sha1-7v0VTp550sjTQXtkeo8U2f7M4U4=", + "dependencies": { + "acorn": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-1.2.2.tgz", + "integrity": "sha1-yM4n3grMdtiW0rH6099YjZ6C8BQ=" + } + } + }, + "wordwrap": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=" + }, + "x-xss-protection": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/x-xss-protection/-/x-xss-protection-1.0.0.tgz", + "integrity": "sha1-iYr7k4abJGYc+cUvnujbjtB2Tdk=" + }, + "xtend": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" + }, + "yargs": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", + "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=" + } + } +} diff --git a/viewmodels/preQualProduct.js b/viewmodels/preQualProduct.js index 832c89d..24d95c3 100644 --- a/viewmodels/preQualProduct.js +++ b/viewmodels/preQualProduct.js @@ -28,7 +28,12 @@ module.exports = function preQualProduct (apiProduct) { 'priority', 'tier', 'terms', - 'additionalInformationUrl' + 'additionalInformationUrl', + 'introPurchaseApr', + 'purchaseApr', + 'introBalanceTransferApr', + 'annualMembershipFee', + 'creditRating' ]) viewModel.productDisplayName = sanitize(apiProduct.productDisplayName || '???') diff --git a/views/index.jade b/views/index.jade index fff5291..5eb2f8e 100644 --- a/views/index.jade +++ b/views/index.jade @@ -49,7 +49,7 @@ block content div.summary +cardNameImage(card)(data-toggle='tooltip', title='#{(card.productKeywords || []).join(", ")}') +marketingCopy(card) - div.apply + div.apply.json-toggle.pull-right(title="Apply Now Link Disabled in Sandbox") +applyButton(card) p with CapitalOne sup ® @@ -89,18 +89,3 @@ block modals | See Offers include ./includes/raw-json-modal -block scripts - script. - $(function () { - $('.card-info [data-toggle="tooltip"]').tooltip({ delay: 200, placement: 'right' }); - }); - $(function () { - $('.card-info .json-toggle').click(function (evt) { - var card = $(this).closest('.card-info'), - json = JSON.stringify(card.data('rawJson'), null, 2); - evt.preventDefault(); - - $('#card-json .raw-json').text(json); - $('#card-json').modal('show'); - }); - }); diff --git a/views/layout.jade b/views/layout.jade index b28a41b..90beec0 100644 --- a/views/layout.jade +++ b/views/layout.jade @@ -24,7 +24,7 @@ mixin cardNameImage(card) img.card-name(src='/images/default-card.png')&attributes(attributes) mixin applyButton(card) - a.btn.btn.btn-lg.btn-success(href=card.applyNowLink) + a.btn.btn.btn-lg.btn-success i.fa.fa-lock(aria-hidden="true") | APPLY NOW @@ -86,4 +86,17 @@ head .on('hide.bs.collapse', function (e) { $(e.target).siblings('a.show-hide').text('Show More') }) - }) + }); + $(function () { + $('.card-info [data-toggle="tooltip"]').tooltip({delay: 0, placement: 'right'}); + }); + $(function () { + $('.card-info a.json-toggle').click(function (evt) { + var card = $(this).closest('.card-info'), + json = JSON.stringify(card.data('rawJson'), null, 2); + evt.preventDefault(); + + $('#card-json .raw-json').text(json); + $('#card-json').modal('show'); + }); + }); diff --git a/views/offers.jade b/views/offers.jade index 2398505..2eb4876 100644 --- a/views/offers.jade +++ b/views/offers.jade @@ -40,10 +40,26 @@ block content div.summary +cardNameImage(card) +marketingCopy(card) - div.apply + div.apply.json-toggle.pull-right(title="Apply Now Link Disabled in Sandbox") +applyButton(card) p with CapitalOne sup ® + div.additional-info + div.info + div.info-label Purchases Intro APR + div.info-value= card.introPurchaseApr.introPurchaseAprDescription || 'N/A' + div.info + div.info-label Balance Transfers Intro APR + div.info-value= card.introBalanceTransferApr.introBalanceTransferAprDescription || 'N/A' + div.info + div.info-label Regular APR + div.info-value= card.purchaseApr.purchaseAprDescription || 'N/A' + div.info + div.info-label Annual Fee + div.info-value= card.annualMembershipFee || 'N/A' + div.info + div.info-label Credit Needed + div.info-value= (card.creditRating || []).join(', ') || 'N/A' footer#prequal-stats-summary.navbar-fixed-bottom i.fa.fa-pie-chart(aria-hidden="true") div.stat-text Loading Stats... @@ -107,14 +123,3 @@ block scripts $('#prequal-stats-summary .stat-text').html('FAILED to get prequalification summary data!') }) }) - - $(function () { - $('.card-info .json-toggle').click(function (evt) { - var card = $(this).closest('.card-info'), - json = JSON.stringify(card.data('rawJson'), null, 2); - evt.preventDefault(); - - $('#card-json .raw-json').text(json); - $('#card-json').modal('show'); - }); - }); From 98b4fb8f315be34a32f8daaa47f53e04d8fc95eb Mon Sep 17 00:00:00 2001 From: Em Date: Mon, 17 Jul 2017 14:46:02 -0400 Subject: [PATCH 119/147] Adding more files to gitignore --- .gitignore | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.gitignore b/.gitignore index b6d0cc0..cbe7837 100644 --- a/.gitignore +++ b/.gitignore @@ -38,3 +38,9 @@ jspm_packages # Optional REPL history .node_repl_history + +# Optional IDEA file +.idea + +# Package-lock JSON +package-lock.json From 14ef895b3e8c4beaaa68261585a606c17124f0b1 Mon Sep 17 00:00:00 2001 From: Em Date: Mon, 17 Jul 2017 14:47:18 -0400 Subject: [PATCH 120/147] Adding APR to prequal and disabling Apply Now link --- package-lock.json | 1230 --------------------------------------------- 1 file changed, 1230 deletions(-) delete mode 100644 package-lock.json diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 2ae1733..0000000 --- a/package-lock.json +++ /dev/null @@ -1,1230 +0,0 @@ -{ - "name": "credit-offers", - "version": "2.0.0", - "lockfileVersion": 1, - "dependencies": { - "accepts": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.2.13.tgz", - "integrity": "sha1-5fHzkoxtlf2WVYw27D2dDeSm7Oo=" - }, - "acorn": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-2.7.0.tgz", - "integrity": "sha1-q259nYhqrKiwhbwzEreaGYQz8Oc=" - }, - "acorn-globals": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-1.0.9.tgz", - "integrity": "sha1-VbtemGkVB7dFedBRNBMhfDgMVM8=" - }, - "align-text": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", - "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=" - }, - "amdefine": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", - "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=" - }, - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" - }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" - }, - "array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" - }, - "asap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/asap/-/asap-1.0.0.tgz", - "integrity": "sha1-sqRdpf36ILBJb8N2jMJ8EvqRan0=" - }, - "asn1": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", - "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=" - }, - "assert-plus": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz", - "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=" - }, - "async": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/async/-/async-2.5.0.tgz", - "integrity": "sha512-e+lJAJeNWuPCNyxZKOBdaJGyLGHugXVQtrAwtuAe2vhxTYxFTKE73p8JuTmdH0qdQZtDvI4dhJwjZc5zsfIsYw==", - "dependencies": { - "lodash": { - "version": "4.17.4", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", - "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=" - } - } - }, - "aws-sign2": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz", - "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=" - }, - "aws4": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", - "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=" - }, - "basic-auth": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-1.0.4.tgz", - "integrity": "sha1-Awk1sB3nyblKgksp8/zLdQ06UpA=" - }, - "bcrypt-pbkdf": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", - "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", - "optional": true - }, - "bl": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/bl/-/bl-1.0.3.tgz", - "integrity": "sha1-/FQhoo/UImA2w7OJGmaiW8ZNIm4=" - }, - "bluebird": { - "version": "3.4.7", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz", - "integrity": "sha1-9y12C+Cbf3bQjtj66Ysomo0F+rM=" - }, - "body-parser": { - "version": "1.13.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.13.3.tgz", - "integrity": "sha1-wIzzMMM1jhUQFqBXRvE/ApyX+pc=" - }, - "boom": { - "version": "2.10.1", - "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", - "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=" - }, - "bootstrap": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-3.3.7.tgz", - "integrity": "sha1-WjiTlFSfIzMIdaOxUGVldPip63E=" - }, - "bytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-2.1.0.tgz", - "integrity": "sha1-rJPEEOL/ycx89LRks4KJBn9eR7Q=" - }, - "camelcase": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", - "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=" - }, - "camelize": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.0.tgz", - "integrity": "sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs=" - }, - "caseless": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz", - "integrity": "sha1-cVuW6phBWTzDMGeSP17GDr2k99c=" - }, - "center-align": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", - "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=" - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=" - }, - "character-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/character-parser/-/character-parser-1.2.1.tgz", - "integrity": "sha1-wN3kqxgnE7kZuXCVmhI+zBow/NY=" - }, - "clean-css": { - "version": "3.4.27", - "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-3.4.27.tgz", - "integrity": "sha1-re91sxwWD/pdcvTeZ5ZuJmDBolU=", - "dependencies": { - "commander": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.8.1.tgz", - "integrity": "sha1-Br42f+v9oMMwqh4qBy09yXYkJdQ=" - } - } - }, - "cliui": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", - "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", - "dependencies": { - "wordwrap": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", - "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=" - } - } - }, - "combined-stream": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", - "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=" - }, - "commander": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.6.0.tgz", - "integrity": "sha1-nfflL7Kgyw+4kFjugMMQQiXzfh0=" - }, - "connect": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/connect/-/connect-3.4.0.tgz", - "integrity": "sha1-7oeJo71GBL/aOdvPHTu0gt/mzyQ=", - "dependencies": { - "escape-html": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.2.tgz", - "integrity": "sha1-130y+pjjjC9BroXpJ44ODmuhAiw=" - }, - "finalhandler": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-0.4.0.tgz", - "integrity": "sha1-llpS2ejQXSuFdUhUH7ibU6JJfZs=" - } - } - }, - "constantinople": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/constantinople/-/constantinople-3.0.2.tgz", - "integrity": "sha1-S5RdmTeQe82Y7ldRIsOBdRZUQUE=" - }, - "content-disposition": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.1.tgz", - "integrity": "sha1-h0dsamfI2qh+Muh2Ft+IO6f7Bxs=" - }, - "content-security-policy-builder": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/content-security-policy-builder/-/content-security-policy-builder-1.0.0.tgz", - "integrity": "sha1-Ef1AxcwpimxyWjX5rPcegqtdMkM=" - }, - "content-type": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.2.tgz", - "integrity": "sha1-t9ETrueo3Se9IRM8TcJSnfFyHu0=" - }, - "cookie": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.1.3.tgz", - "integrity": "sha1-5zSlwUF/zkctWu+Cw4HKu2TRpDU=" - }, - "cookie-parser": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.3.5.tgz", - "integrity": "sha1-nXVVcPtdF4kHcSJ6AjFNm+fPg1Y=" - }, - "cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" - }, - "cryptiles": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", - "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=" - }, - "csrf": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/csrf/-/csrf-3.0.6.tgz", - "integrity": "sha1-thEg3c7q/JHnbtUxO7XAsmZ7cQo=" - }, - "css": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/css/-/css-1.0.8.tgz", - "integrity": "sha1-k4aBHKgrzMnuf7WnMrHioxfIo+c=" - }, - "css-parse": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/css-parse/-/css-parse-1.0.4.tgz", - "integrity": "sha1-OLBQP7+dqfVOnB29pg4UXHcRe90=" - }, - "css-stringify": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/css-stringify/-/css-stringify-1.0.5.tgz", - "integrity": "sha1-sNBClG2ylTu50pKQCmy19tASIDE=" - }, - "csurf": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/csurf/-/csurf-1.8.3.tgz", - "integrity": "sha1-I/KhO/HY/OHQyZZYg5RELLqGpWo=" - }, - "dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" - } - } - }, - "dashify": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/dashify/-/dashify-0.2.2.tgz", - "integrity": "sha1-agdBWgHJH69KMuONnfunH2HLIP4=" - }, - "debug": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", - "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=" - }, - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" - }, - "depd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.0.1.tgz", - "integrity": "sha1-gK7GTJ1tl+ZcwqnKqTwKpqv3Oqo=" - }, - "destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" - }, - "dns-prefetch-control": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/dns-prefetch-control/-/dns-prefetch-control-0.1.0.tgz", - "integrity": "sha1-YN20V3dOF48flBXwyrsOhbCzALI=" - }, - "dom-serializer": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz", - "integrity": "sha1-BzxpdUbOB4DOI75KKOKT5AvDDII=", - "dependencies": { - "domelementtype": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz", - "integrity": "sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs=" - } - } - }, - "domelementtype": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.0.tgz", - "integrity": "sha1-sXrtguirWeUt2cGbF1bg/BhyBMI=" - }, - "domhandler": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.1.tgz", - "integrity": "sha1-iS5HAAqZvlW783dP/qBWHYh5wlk=" - }, - "domutils": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.6.2.tgz", - "integrity": "sha1-GVjMC0yUJuntNn+xyOhUiRsPo/8=" - }, - "dont-sniff-mimetype": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/dont-sniff-mimetype/-/dont-sniff-mimetype-1.0.0.tgz", - "integrity": "sha1-WTKJDcn04vGeXrAqIAJuXl78j1g=" - }, - "ecc-jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", - "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", - "optional": true - }, - "ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" - }, - "ejs": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.3.4.tgz", - "integrity": "sha1-PHbKoJZks1g7ADevncE2557Gi5g=" - }, - "entities": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.1.tgz", - "integrity": "sha1-blwtClYhtdra7O+AuQ7ftc13cvA=" - }, - "escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" - }, - "etag": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.7.0.tgz", - "integrity": "sha1-A9MLX2fdbmMtKUXTDWZScxo01dg=" - }, - "express": { - "version": "4.13.4", - "resolved": "https://registry.npmjs.org/express/-/express-4.13.4.tgz", - "integrity": "sha1-PAt288d1kMg0VzkGHsC9O6Bn7CQ=", - "dependencies": { - "cookie": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.1.5.tgz", - "integrity": "sha1-armUiksa4hlSzSWIUwpHItQETXw=" - }, - "depd": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.0.tgz", - "integrity": "sha1-4b2Cxqq2ztlluXuIsX7T5SjKGMM=" - } - } - }, - "express-validator": { - "version": "2.21.0", - "resolved": "https://registry.npmjs.org/express-validator/-/express-validator-2.21.0.tgz", - "integrity": "sha1-9fwvn6npqFeGNPEOhrpaQ0a5b08=", - "dependencies": { - "lodash": { - "version": "4.16.6", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.16.6.tgz", - "integrity": "sha1-0iyaxmAojzhD4Wun0rXQbMon13c=" - } - } - }, - "extend": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", - "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=" - }, - "extsprintf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.0.2.tgz", - "integrity": "sha1-4QgOBljjALBilJkMxw4VAiNf1VA=" - }, - "finalhandler": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-0.4.1.tgz", - "integrity": "sha1-haF8bFmpRxfSYtYSMNSw6+PUoU0=" - }, - "font-awesome": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/font-awesome/-/font-awesome-4.7.0.tgz", - "integrity": "sha1-j6jPBBGhoxr9B7BtKQK7n8gVoTM=" - }, - "forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" - }, - "form-data": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-1.0.1.tgz", - "integrity": "sha1-rjFduaSQf6BlUCMEpm13M0de43w=" - }, - "forwarded": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.0.tgz", - "integrity": "sha1-Ge+YdMSuHCl7zweP3mOgm2aoQ2M=" - }, - "frameguard": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/frameguard/-/frameguard-1.0.0.tgz", - "integrity": "sha1-PMesF07JCSG712mQgKjyIfXAADk=" - }, - "fresh": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.3.0.tgz", - "integrity": "sha1-ZR+DjiJCTnVm3hYdg1jKoZn4PU8=" - }, - "generate-function": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz", - "integrity": "sha1-aFj+fAlpt9TpCTM3ZHrHn2DfvnQ=" - }, - "generate-object-property": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz", - "integrity": "sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=" - }, - "getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" - } - } - }, - "graceful-readlink": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", - "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=" - }, - "har-validator": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.6.tgz", - "integrity": "sha1-zcvAgYgmWtEZtqWnyKtw7s+10n0=", - "dependencies": { - "commander": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz", - "integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==" - } - } - }, - "has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=" - }, - "hawk": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", - "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=" - }, - "helmet": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/helmet/-/helmet-1.1.0.tgz", - "integrity": "sha1-HdWea0Vo/b5Ku9tgy0F06wM4KaY=" - }, - "helmet-csp": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/helmet-csp/-/helmet-csp-1.0.3.tgz", - "integrity": "sha1-FNBkHs81IK6fc3vKE4RKf09kHXI=" - }, - "hide-powered-by": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/hide-powered-by/-/hide-powered-by-1.0.0.tgz", - "integrity": "sha1-SoWtZYgfYoV/xwr3F0oRhNzM4ys=" - }, - "hoek": { - "version": "2.16.3", - "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", - "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=" - }, - "hpkp": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/hpkp/-/hpkp-1.0.0.tgz", - "integrity": "sha1-hIPkqarIBVtgPkK5AiIaB4y/VfE=" - }, - "hsts": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/hsts/-/hsts-1.0.0.tgz", - "integrity": "sha1-mOEDnverpVQFe2sOMlhMCxFDpBQ=" - }, - "html-entities": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.2.0.tgz", - "integrity": "sha1-QZSMr4XOgv7Tbk5qDtNxpmZDeeI=" - }, - "htmlparser2": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.9.2.tgz", - "integrity": "sha1-G9+HrMoPP55T+k/M6w9LTLsAszg=" - }, - "http-errors": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.3.1.tgz", - "integrity": "sha1-GX4izevUGYWF6GlO9nhhl7ke2UI=" - }, - "http-signature": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", - "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=" - }, - "iconv-lite": { - "version": "0.4.11", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.11.tgz", - "integrity": "sha1-LstC/SlHRJIiCaLnxATayHk9it4=" - }, - "ienoopen": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/ienoopen/-/ienoopen-1.0.0.tgz", - "integrity": "sha1-NGpCj0dKrI9QzzeE6i0PFvYr2ms=" - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - }, - "ipaddr.js": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.0.5.tgz", - "integrity": "sha1-X6eM8wG4JceKvDBC2BJyMEnqI8c=" - }, - "is-buffer": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.5.tgz", - "integrity": "sha1-Hzsm72E7IUuIy8ojzGwB2Hlh7sw=" - }, - "is-my-json-valid": { - "version": "2.16.0", - "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.16.0.tgz", - "integrity": "sha1-8Hndm/2uZe4gOKrorLyGqxCeNpM=" - }, - "is-promise": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", - "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=" - }, - "is-property": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", - "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=" - }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, - "isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" - }, - "jade": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/jade/-/jade-1.11.0.tgz", - "integrity": "sha1-nIDlOMEtP7lcjZu5VZ+gzAQEBf0=" - }, - "jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "optional": true - }, - "json-schema": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" - }, - "json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" - }, - "jsonpointer": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz", - "integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk=" - }, - "jsprim": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.0.tgz", - "integrity": "sha1-o7h+QCmNjDgFUtjMdiigu5WiKRg=", - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" - } - } - }, - "jstransformer": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/jstransformer/-/jstransformer-0.0.2.tgz", - "integrity": "sha1-eq4pqQPRls+glz2IXT5HlH7Ndqs=" - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=" - }, - "lazy-cache": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", - "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=" - }, - "lodash": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.1.0.tgz", - "integrity": "sha1-KZiUKD3gGp7vvt/0xLmwCmoubpY=" - }, - "lodash._baseassign": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz", - "integrity": "sha1-jDigmVAPIVrQnlnxci/QxSv+Ck4=" - }, - "lodash._basecallback": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/lodash._basecallback/-/lodash._basecallback-3.3.1.tgz", - "integrity": "sha1-t7K7Q9whYEJKIczybFfkQ3cqjic=" - }, - "lodash._basecopy": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", - "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=" - }, - "lodash._baseeach": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/lodash._baseeach/-/lodash._baseeach-3.0.4.tgz", - "integrity": "sha1-z4cGVyyhROjZ11InyZDamC+TKvM=" - }, - "lodash._baseisequal": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/lodash._baseisequal/-/lodash._baseisequal-3.0.7.tgz", - "integrity": "sha1-2AJfdjOdKTQnZ9zIh85cuVpbUfE=" - }, - "lodash._basereduce": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/lodash._basereduce/-/lodash._basereduce-3.0.2.tgz", - "integrity": "sha1-E/uY+94WIIOgyWfwYFwyrPuycLI=" - }, - "lodash._bindcallback": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz", - "integrity": "sha1-5THCdkTPi1epnhftlbNcdIeJOS4=" - }, - "lodash._createassigner": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/lodash._createassigner/-/lodash._createassigner-3.1.1.tgz", - "integrity": "sha1-g4pbri/aymOsIt7o4Z+k5taXCxE=" - }, - "lodash._getnative": { - "version": "3.9.1", - "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", - "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=" - }, - "lodash._isiterateecall": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", - "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=" - }, - "lodash.assign": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-3.2.0.tgz", - "integrity": "sha1-POnwI0tLIiPilrj6CsH+6OvKZPo=" - }, - "lodash.isarguments": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", - "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=" - }, - "lodash.isarray": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", - "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=" - }, - "lodash.isfunction": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/lodash.isfunction/-/lodash.isfunction-3.0.6.tgz", - "integrity": "sha1-M+WojjtEQw7elx60Pa8VntHd45U=" - }, - "lodash.isstring": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-3.0.1.tgz", - "integrity": "sha1-QWOJROoELvZ61nwpOqVB0/PW5Tw=" - }, - "lodash.istypedarray": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/lodash.istypedarray/-/lodash.istypedarray-3.0.6.tgz", - "integrity": "sha1-yaR3SYYHUB2OhJTSg7h8OSgc72I=" - }, - "lodash.keys": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", - "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=" - }, - "lodash.pairs": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash.pairs/-/lodash.pairs-3.0.1.tgz", - "integrity": "sha1-u+CNV4bu6qCaFckevw3LfSvjJqk=" - }, - "lodash.reduce": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/lodash.reduce/-/lodash.reduce-3.1.2.tgz", - "integrity": "sha1-KvPixoig2TnYbqcXFOjNU84yewI=" - }, - "lodash.restparam": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz", - "integrity": "sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=" - }, - "lodash.some": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/lodash.some/-/lodash.some-3.2.3.tgz", - "integrity": "sha1-djN9pP4E8NMvEMk3ha8PYFv+Lq8=" - }, - "longest": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", - "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=" - }, - "media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" - }, - "merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" - }, - "methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" - }, - "mime": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.3.4.tgz", - "integrity": "sha1-EV+eO2s9rylZmDyzjxSaLUDrXVM=" - }, - "mime-db": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.27.0.tgz", - "integrity": "sha1-gg9XIpa70g7CXtVeW13oaeVDbrE=" - }, - "mime-types": { - "version": "2.1.15", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.15.tgz", - "integrity": "sha1-pOv1BkCUVpI3uM9wBGd20J/JKu0=" - }, - "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" - }, - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=" - }, - "moment": { - "version": "2.16.0", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.16.0.tgz", - "integrity": "sha1-848sl8mImw7hj8bMOS4eRDrS2o4=" - }, - "morgan": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.6.1.tgz", - "integrity": "sha1-X9gYOYxoGcuiinzWZk8pL+HAu/I=" - }, - "ms": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", - "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=" - }, - "negotiator": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.5.3.tgz", - "integrity": "sha1-Jp1cR2gQ7JLtvntsLygxY4T5p+g=" - }, - "nocache": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/nocache/-/nocache-1.0.0.tgz", - "integrity": "sha1-MgZe+F9uYqAUVCwrK68RuzcE3yE=" - }, - "node-uuid": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz", - "integrity": "sha1-sEDrCSOWivq/jTL7HxfxFn/auQc=" - }, - "oauth-sign": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", - "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=" - }, - "on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=" - }, - "on-headers": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.1.tgz", - "integrity": "sha1-ko9dD0cNSTQmUepnlLCFfBAGk/c=" - }, - "optimist": { - "version": "0.3.7", - "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.3.7.tgz", - "integrity": "sha1-yQlBrVnkJzMokjB00s8ufLxuwNk=" - }, - "parseurl": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.1.tgz", - "integrity": "sha1-yKuMkiO6NIiKpkopeyiFO+wY2lY=" - }, - "path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" - }, - "pinkie": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=" - }, - "pinkie-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=" - }, - "platform": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/platform/-/platform-1.3.0.tgz", - "integrity": "sha1-VrTFp0jyCjyBXIMWj8oo+4u2+9Q=" - }, - "process-nextick-args": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", - "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=" - }, - "promise": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/promise/-/promise-6.1.0.tgz", - "integrity": "sha1-LOcp9rlLRcJoka0GAsXJDgTG7vY=" - }, - "proxy-addr": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-1.0.10.tgz", - "integrity": "sha1-DUCoL4Afw1VWfS7LZe/j8HfxIcU=" - }, - "qs": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-4.0.0.tgz", - "integrity": "sha1-wx2bdOwn33XlQ6hseHKO2NRiNgc=" - }, - "random-bytes": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", - "integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs=" - }, - "range-parser": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.0.3.tgz", - "integrity": "sha1-aHKCNTXGkuLCoBA4Jq/YLC4P8XU=" - }, - "raw-body": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.1.7.tgz", - "integrity": "sha1-rf6s4uT7MJgFgBTQjActzFl1h3Q=", - "dependencies": { - "bytes": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-2.4.0.tgz", - "integrity": "sha1-fZcZb51br39pNeJZhVSe3SpsIzk=" - }, - "iconv-lite": { - "version": "0.4.13", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.13.tgz", - "integrity": "sha1-H4irpKsLFQjoMSrMOTRfNumS4vI=" - } - } - }, - "readable-stream": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", - "integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=" - }, - "regexp-quote": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/regexp-quote/-/regexp-quote-0.0.0.tgz", - "integrity": "sha1-Hg9GUMhi3L/tVP1CsUjpuxch/PI=" - }, - "repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=" - }, - "request": { - "version": "2.69.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.69.0.tgz", - "integrity": "sha1-z5HS4AB1KxIXFVwAUkGRGZGiNGo=", - "dependencies": { - "qs": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.0.4.tgz", - "integrity": "sha1-UQGdhHIMk5uCc36EVWp4Izjs6ns=" - } - } - }, - "right-align": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", - "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=" - }, - "rndm": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/rndm/-/rndm-1.2.0.tgz", - "integrity": "sha1-8z/pz7Urv9UgqhgyO8ZdsRCht2w=" - }, - "sanitize-html": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-1.13.0.tgz", - "integrity": "sha1-TuF8vsUWv+MvLOZoaladfmtPNjE=" - }, - "send": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/send/-/send-0.13.1.tgz", - "integrity": "sha1-ow1fTILIqbrprQCh2bG9vm8Zntc=", - "dependencies": { - "depd": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.0.tgz", - "integrity": "sha1-4b2Cxqq2ztlluXuIsX7T5SjKGMM=" - }, - "statuses": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.2.1.tgz", - "integrity": "sha1-3e1FzBglbVHtQK7BQkidXGECbSg=" - } - } - }, - "serve-favicon": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/serve-favicon/-/serve-favicon-2.3.0.tgz", - "integrity": "sha1-rtNsxoNAaabxicxyIsahqBHcWzk=" - }, - "serve-static": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.10.3.tgz", - "integrity": "sha1-zlpuzTEB/tXsCYJ9rCKpwpv7BTU=", - "dependencies": { - "depd": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.0.tgz", - "integrity": "sha1-4b2Cxqq2ztlluXuIsX7T5SjKGMM=" - }, - "send": { - "version": "0.13.2", - "resolved": "https://registry.npmjs.org/send/-/send-0.13.2.tgz", - "integrity": "sha1-dl52B8gFVFK7pvCwUllTUJhgNt4=" - }, - "statuses": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.2.1.tgz", - "integrity": "sha1-3e1FzBglbVHtQK7BQkidXGECbSg=" - } - } - }, - "sntp": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", - "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=" - }, - "source-map": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", - "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=" - }, - "sshpk": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz", - "integrity": "sha1-US322mKHFEMW3EwY/hzx2UBzm+M=", - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" - } - } - }, - "statuses": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", - "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=" - }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" - }, - "stringstream": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", - "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=" - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=" - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" - }, - "tough-cookie": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.2.2.tgz", - "integrity": "sha1-yDoYMPTl7wuT7yo0iOck+N4Basc=" - }, - "transformers": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/transformers/-/transformers-2.1.0.tgz", - "integrity": "sha1-XSPLNVYd2F3Gf7hIIwm0fVPM6ac=", - "dependencies": { - "is-promise": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-1.0.1.tgz", - "integrity": "sha1-MVc3YcBX4zwukaq56W2gjO++duU=" - }, - "promise": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/promise/-/promise-2.0.0.tgz", - "integrity": "sha1-RmSKqdYFr10ucMMCS/WUNtoCuA4=" - }, - "source-map": { - "version": "0.1.43", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz", - "integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=" - }, - "uglify-js": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.2.5.tgz", - "integrity": "sha1-puAqcNg5eSuXgEiLe4sYTAlcmcc=" - } - } - }, - "tsscmp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.5.tgz", - "integrity": "sha1-fcSjOvcVgatDN9qR2FylQn69mpc=" - }, - "tunnel-agent": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz", - "integrity": "sha1-Y3PbdpCf5XDgjXNYM2Xtgop07us=" - }, - "tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", - "optional": true - }, - "type-is": { - "version": "1.6.15", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.15.tgz", - "integrity": "sha1-yrEPtJCeRByChC6v4a1kbIGARBA=" - }, - "uglify-js": { - "version": "2.8.29", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", - "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", - "dependencies": { - "source-map": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", - "integrity": "sha1-dc449SvwczxafwwRjYEzSiu19BI=" - } - } - }, - "uglify-to-browserify": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", - "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", - "optional": true - }, - "uid-safe": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.4.tgz", - "integrity": "sha1-Otbzg2jG1MjHXsF2I/t5qh0HHYE=" - }, - "unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" - }, - "utils-merge": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.0.tgz", - "integrity": "sha1-ApT7kiu5N1FTVBxPcJYjHyh8ivg=" - }, - "validator": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/validator/-/validator-5.7.0.tgz", - "integrity": "sha1-eoelgUa2laxIYHEUHAxJ1n2gXlw=" - }, - "vary": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.0.1.tgz", - "integrity": "sha1-meSYFWaihhGN+yuBc1ffeZM3bRA=" - }, - "verror": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.3.6.tgz", - "integrity": "sha1-z/XfEpRtKX0rqu+qJoniW+AcAFw=" - }, - "void-elements": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", - "integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=" - }, - "window-size": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", - "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=" - }, - "with": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/with/-/with-4.0.3.tgz", - "integrity": "sha1-7v0VTp550sjTQXtkeo8U2f7M4U4=", - "dependencies": { - "acorn": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-1.2.2.tgz", - "integrity": "sha1-yM4n3grMdtiW0rH6099YjZ6C8BQ=" - } - } - }, - "wordwrap": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", - "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=" - }, - "x-xss-protection": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/x-xss-protection/-/x-xss-protection-1.0.0.tgz", - "integrity": "sha1-iYr7k4abJGYc+cUvnujbjtB2Tdk=" - }, - "xtend": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", - "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" - }, - "yargs": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", - "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=" - } - } -} From a8be93b5f90131dd56b172b1e63e9817ce5c3121 Mon Sep 17 00:00:00 2001 From: Em Date: Mon, 17 Jul 2017 14:49:18 -0400 Subject: [PATCH 121/147] Update README.md --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index d5efd4e..8dd9f5b 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,6 @@ Navigate to http://localhost:3000. This will retrieve a list of Consumer card p * In sandbox mode, “Apply now” button will not work * Toggle the card type to 'Business' to request and display a list of business card products from the API - * In sandbox mode, "Apply now" button will not work * Click on the 'Find Pre-Qualified Offers' button to launch a simple customer information form and test out the pre-qualification API behavior. The results screen will also perform two asynchronous calls: * POST to `/credit-offers/prequalifications/{prequalificationId}` to acknowledge that the results were displayed to the customer * GET from the `/credit-offers/prequalifications-summary` endpoint to display simple pre-qualification statistics at the bottom of the page From 88f93937b72413d79eba369825702d8cf6606436 Mon Sep 17 00:00:00 2001 From: Em Date: Mon, 17 Jul 2017 14:53:57 -0400 Subject: [PATCH 122/147] Terms is no longer in prequalification products object --- viewmodels/preQualProduct.js | 1 - 1 file changed, 1 deletion(-) diff --git a/viewmodels/preQualProduct.js b/viewmodels/preQualProduct.js index 24d95c3..9ca489a 100644 --- a/viewmodels/preQualProduct.js +++ b/viewmodels/preQualProduct.js @@ -27,7 +27,6 @@ module.exports = function preQualProduct (apiProduct) { 'code', 'priority', 'tier', - 'terms', 'additionalInformationUrl', 'introPurchaseApr', 'purchaseApr', From 47f10b6ea557ec15c16537bcec8b1ac215ad6c7e Mon Sep 17 00:00:00 2001 From: Hogan Date: Tue, 17 Oct 2017 15:26:03 -0700 Subject: [PATCH 123/147] added login button --- views/includes/login-modal.jade | 25 +++++++++++++++++++++++++ views/index.jade | 13 +++++++++---- 2 files changed, 34 insertions(+), 4 deletions(-) create mode 100644 views/includes/login-modal.jade diff --git a/views/includes/login-modal.jade b/views/includes/login-modal.jade new file mode 100644 index 0000000..f65bd37 --- /dev/null +++ b/views/includes/login-modal.jade @@ -0,0 +1,25 @@ +//- Copyright 2017 Capital One Services, LLC +//- +//- Licensed under the Apache License, Version 2.0 (the "License"); +//- you may not use this file except in compliance with the License. +//- You may obtain a copy of the License at +//- +//- http://www.apache.org/licenses/LICENSE-2.0 +//- +//- Unless required by applicable law or agreed to in writing, software +//- distributed under the License is distributed on an "AS IS" BASIS, +//- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//- See the License for the specific language governing permissions and limitations under the License. +//- +//- SPDX-Copyright: Copyright (c) Capital One Services, LLC +//- SPDX-License-Identifier: Apache-2.0 + +div#login.modal.fade(tabindex='-1', role='dialog') + div.modal-dialog + div.modal-content + div.modal-header + button.close(type='button', data-dismiss='modal', aria-label='close') + span(aria-hidden='true') × + h4.modal-title Raw Card JSON + div.modal-body + pre.raw-json diff --git a/views/index.jade b/views/index.jade index 5eb2f8e..1cf028e 100644 --- a/views/index.jade +++ b/views/index.jade @@ -17,9 +17,14 @@ extends ./layout.jade block navbar-custom - button.navbar-right.btn.btn-md.navbar-btn.btn-success(role='button', data-toggle='modal', data-target='#customer-info') - i.btn-img.fa.fa-credit-card(aria-hidden="true") - | Find Pre-Qualified Offers + .btn-toolbar + button.navbar-right.btn.btn-md.navbar-btn.btn-success(role='button', data-toggle='modal', data-target='#login') + i.btn-img.fa.fa-sign-in(aria-hidden="true") + | Sign In + button.navbar-right.btn.btn-md.navbar-btn.btn-success(role='button', data-toggle='modal', data-target='#customer-info') + i.btn-img.fa.fa-credit-card(aria-hidden="true") + | Find Pre-Qualified Offers + block content div.header @@ -88,4 +93,4 @@ block modals button.btn.btn-primary(type='submit') | See Offers include ./includes/raw-json-modal - + include ./includes/login-modal From 21c378de797e655ce6e470664b8376bddbd3a3ac Mon Sep 17 00:00:00 2001 From: Hogan Date: Wed, 18 Oct 2017 14:00:03 -0700 Subject: [PATCH 124/147] login modal form --- views/includes/login-modal.jade | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/views/includes/login-modal.jade b/views/includes/login-modal.jade index f65bd37..3c68a3a 100644 --- a/views/includes/login-modal.jade +++ b/views/includes/login-modal.jade @@ -17,9 +17,21 @@ div#login.modal.fade(tabindex='-1', role='dialog') div.modal-dialog div.modal-content + form(action='/login', method='POST') + input(type="hidden" name="_csrf" value="#{csrfToken}") div.modal-header - button.close(type='button', data-dismiss='modal', aria-label='close') - span(aria-hidden='true') × - h4.modal-title Raw Card JSON + button.close(type='button', data-dismiss='modal', aria-label='close') + span(aria-hidden='true') × + h4.modal-title Sign In div.modal-body - pre.raw-json + div.form-group(class={required: true}) + label(for='username')= 'Username' + input.form-control(type='text', name='username', required=true) + div.form-group(class={required: true}) + label(for='password')= 'Password' + input.form-control(type='text', name='password', required=true) + div.modal-footer + button.btn.btn-default(type='button', data-dismiss='modal') + | Close + button.btn.btn-primary(type='submit') + | Sign In From c4f69cf29530f73219a360d3ab34c7d94bc1b05b Mon Sep 17 00:00:00 2001 From: Hogan Date: Fri, 27 Oct 2017 16:30:23 -0700 Subject: [PATCH 125/147] login modal sandbox defaults --- views/includes/login-modal.jade | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/views/includes/login-modal.jade b/views/includes/login-modal.jade index 3c68a3a..47a168f 100644 --- a/views/includes/login-modal.jade +++ b/views/includes/login-modal.jade @@ -13,23 +13,23 @@ //- //- SPDX-Copyright: Copyright (c) Capital One Services, LLC //- SPDX-License-Identifier: Apache-2.0 - +- sandboxEnv = this.process.env.NODE_ENV == 'sandbox' div#login.modal.fade(tabindex='-1', role='dialog') div.modal-dialog div.modal-content - form(action='/login', method='POST') + form(action='/login', method='POST', name="login") input(type="hidden" name="_csrf" value="#{csrfToken}") div.modal-header button.close(type='button', data-dismiss='modal', aria-label='close') span(aria-hidden='true') × h4.modal-title Sign In div.modal-body - div.form-group(class={required: true}) + div.form-group(class={required: truth}) label(for='username')= 'Username' - input.form-control(type='text', name='username', required=true) - div.form-group(class={required: true}) + input.form-control(type='text', name='username', required='true', value=sandboxEnv ? 'jsmith' : '') + div.form-group(class={required: truth}) label(for='password')= 'Password' - input.form-control(type='text', name='password', required=true) + input.form-control(type='password', name='password', required='true', value=sandboxEnv ? 'Reference@123' : '') div.modal-footer button.btn.btn-default(type='button', data-dismiss='modal') | Close From 587e389b15a23542349c25db545daefe52746355 Mon Sep 17 00:00:00 2001 From: Hogan Date: Fri, 27 Oct 2017 16:34:55 -0700 Subject: [PATCH 126/147] handle signin/signout --- creditoffers/products.js | 31 +++++++++++++++------ creditoffers/users.js | 60 ++++++++++++++++++++++++++++++++++++++++ package.json | 2 ++ 3 files changed, 85 insertions(+), 8 deletions(-) create mode 100644 creditoffers/users.js diff --git a/creditoffers/products.js b/creditoffers/products.js index 7666dc6..341f235 100644 --- a/creditoffers/products.js +++ b/creditoffers/products.js @@ -24,7 +24,7 @@ var _ = require('lodash') * Contains all functions for interacting with the credit offer product listings API * @param {object} client The API client */ -function Products (client) { +function Products(client) { if (!this instanceof Products) { return new Products(client) } @@ -38,7 +38,7 @@ module.exports = Products * @param {object} pagingOptions Optionally control the number of results and starting offset * in the result set */ -Products.prototype.getAll = function getAll (pagingOptions, callback) { +Products.prototype.getAll = function getAll(pagingOptions, callback) { var query = _.pick(pagingOptions, ['limit', 'offset']) this.client.sendRequest({ @@ -54,7 +54,7 @@ Products.prototype.getAll = function getAll (pagingOptions, callback) { * @param {object} pagingOptions Optionally control the number of results and starting offset * in the result set */ -Products.prototype.getAllCards = function getAllCards (pagingOptions, callback) { +Products.prototype.getAllCards = function getAllCards(pagingOptions, callback) { var query = _.pick(pagingOptions, ['limit', 'offset']) this.client.sendRequest({ @@ -71,8 +71,10 @@ Products.prototype.getAllCards = function getAllCards (pagingOptions, callback) * @param {object} pagingOptions Optionally control the number of results and starting offset * in the result set */ -Products.prototype.getCards = function getCards (cardType, pagingOptions, callback) { - if (!cardType) { callback(new Error('A card type must be specified')) } +Products.prototype.getCards = function getCards(cardType, pagingOptions, callback) { + if (!cardType) { + callback(new Error('A card type must be specified')) + } var query = _.pick(pagingOptions, ['limit', 'offset']) this.client.sendRequest({ @@ -88,9 +90,13 @@ Products.prototype.getCards = function getCards (cardType, pagingOptions, callba * @param {string} cardType The type of card (BusinessCard, ConsumerCard) * @param {string} productId The ID of the consumer card product for which to retrieve details */ -Products.prototype.getCardDetail = function getCardDetail (cardType, productId, callback) { - if (!cardType) { callback(new Error('A card type must be specified')) } - if (!productId) { callback(new Error('A product ID must be specified in order to retrieve product details')) } +Products.prototype.getCardDetail = function getCardDetail(cardType, productId, callback) { + if (!cardType) { + callback(new Error('A card type must be specified')) + } + if (!productId) { + callback(new Error('A product ID must be specified in order to retrieve product details')) + } this.client.sendRequest({ url: '/credit-offers/products/' + encodeURIComponent(cardType) + '/' + encodeURIComponent(productId), @@ -98,3 +104,12 @@ Products.prototype.getCardDetail = function getCardDetail (cardType, productId, method: 'GET' }, callback) } + +Products.prototype.postPrefillAcceptance = function postPrefillAcceptance(user, callback) { + this.client.sendRequest({ + url: '/credit-offers/applicant-details', + useOAuth: true, + method: 'POST', + body: user + }, callback) +} diff --git a/creditoffers/users.js b/creditoffers/users.js new file mode 100644 index 0000000..f6cf131 --- /dev/null +++ b/creditoffers/users.js @@ -0,0 +1,60 @@ +module.exports = [{ + username: "rhubbard", + password: "$2a$10$KVkQGwZ0qNxau0tHBpg16O5rym/g3dfKJEd5kCnQ4nOJn1oOjzKca", + details: { + "firstName": "Ray", + "middleName": "G", + "lastName": "Hubbard", + "nameSuffix": "Jr.", + "address": { + "addressLine1": "1230 Duck Fuss Lane", + "addressLine2": "Apt 15X", + "addressLine3": "Room 192", + "addressLine4": "Bed 16", + "city": "Beaumont", + "stateCode": "TX", + "postalCode": "77701", + "addressType": "PhysicalPrimary" + + }, + "telephoneNumbers": [{ + "phoneNumberType": "Home", + "telephoneNumber": "2025550110" + }], + "emailAddresses": [ + "ray@wyliehubbard.com" + ], + "annualIncome": 75000, + "bankAccountSummary": "CheckingOnly" + } + }, + { + username: "jsmith", + password: "$2a$10$KVkQGwZ0qNxau0tHBpg16O5rym/g3dfKJEd5kCnQ4nOJn1oOjzKca", + details: { + "firstName": "Jane", + "lastName": "Smith", + "address": { + "addressLine1": "3828 Piermont Dr", + "addressLine2": "Unit A", + "city": "Albuquerque", + "stateCode": "NM", + "postalCode": "87111", + "addressType": "Mailing" + + }, + "emailAddresses": [ + "jane@example.com" + ], + "telephoneNumbers": [{ + "phoneNumberType": "Work", + "telephoneNumber": "2025550246" + }, { + "phoneNumberType": "Mobile", + "telephoneNumber": "2025551357" + }], + "annualIncome": 100000, + "bankAccountSummary": "Neither" + } + } +] diff --git a/package.json b/package.json index 3681af6..40a4add 100644 --- a/package.json +++ b/package.json @@ -9,9 +9,11 @@ } ], "dependencies": { + "bcrypt": "^1.0.3", "body-parser": "1.13.3", "bootstrap": "3.3.7", "cookie-parser": "1.3.5", + "cookie-session": "^2.0.0-beta.3", "csurf": "1.8.3", "debug": "2.2.0", "ejs": "2.3.4", From 1bb9d7116b94a9444fcebeda6d04fd1eb3490e03 Mon Sep 17 00:00:00 2001 From: Hogan Date: Fri, 27 Oct 2017 16:40:59 -0700 Subject: [PATCH 127/147] handle prefill --- app.js | 18 +++++-- public/css/style.css | 23 +++++++++ public/images/stars_lg.png | Bin 0 -> 1103 bytes public/js/creditoffers.js | 42 +++++++++++++++ routes/index.js | 62 +++++++++++++++++++---- routes/offers.js | 48 +++++++++++------- viewmodels/product.js | 17 +++++-- views/includes/customer-form.jade | 31 ++++++++---- views/includes/prefill-accept-modal.jade | 43 ++++++++++++++++ views/index.jade | 26 +++++++--- views/layout.jade | 3 +- 11 files changed, 257 insertions(+), 56 deletions(-) create mode 100644 public/images/stars_lg.png create mode 100644 public/js/creditoffers.js create mode 100644 views/includes/prefill-accept-modal.jade diff --git a/app.js b/app.js index e8771f5..25b8c70 100644 --- a/app.js +++ b/app.js @@ -21,6 +21,7 @@ var path = require('path') var favicon = require('serve-favicon') var logger = require('morgan') var cookieParser = require('cookie-parser') +var cookieSession = require('cookie-session') var bodyParser = require('body-parser') var expressValidator = require('express-validator') var helmet = require('helmet') @@ -44,11 +45,20 @@ app.set('view engine', 'jade') app.use(favicon(path.join(__dirname, 'public', 'favicon.ico'))) app.use(logger('dev')) app.use(bodyParser.json()) -app.use(bodyParser.urlencoded({ extended: false })) +app.use(bodyParser.urlencoded({ + extended: false +})) app.use(expressValidator({ customValidators: validation.customValidators })) app.use(cookieParser()) +app.use(cookieSession({ + name: 'session', + secret: "b34ejefAt7a3eme5e7paCrEgatadEqEs", + + // Cookie Options + maxAge: 24 * 60 * 60 * 1000 // 24 hours +})) app.use(express.static(path.join(__dirname, 'public'))) // Include the bootstrap package app.use('/css', express.static(path.join(__dirname, 'node_modules/bootstrap/dist/css'))) @@ -65,7 +75,7 @@ app.use('/', index(client)) app.use('/offers', offers(client)) // catch 404 and forward to error handler -app.use(function (req, res, next) { +app.use(function(req, res, next) { var err = new Error('Not Found') err.status = 404 next(err) @@ -76,7 +86,7 @@ app.use(function (req, res, next) { // development error handler // will print stacktrace if (app.get('env') === 'development') { - app.use(function (err, req, res, next) { + app.use(function(err, req, res, next) { res.status(err.status || 500) res.render('error', { message: err.message, @@ -87,7 +97,7 @@ if (app.get('env') === 'development') { // production error handler // no stacktraces leaked to user -app.use(function (err, req, res, next) { +app.use(function(err, req, res, next) { res.status(err.status || 500) res.render('error', { message: err.message, diff --git a/public/css/style.css b/public/css/style.css index 391367a..3a3ac7e 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -38,6 +38,11 @@ body { border-bottom: 1px solid #ccc; } +.bold { + font-weight: bold; + padding: 5px 0; +} + .header { background-color: #efefef; padding-bottom: 10px; } @@ -57,6 +62,24 @@ body { .card-info .card-title { font-size: 1.5em; font-weight: 600; } + .card-info .total-stars { + width: 80px; + height: 16px; + display: inline-block; + background-size: 16px 48px; + margin: 0 5px; + background-image: url('/images/stars_lg.png'); + background-position: 0 -16px; + } + .card-info .actual-stars { + height: 16px; + display: inline-block; + background-size: 16px 48px; + background-image: url('/images/stars_lg.png'); + } + .card-info a.reviews { + font-size: 12px; + } .card-info .main-info { display: flex; flex-flow: row nowrap; } diff --git a/public/images/stars_lg.png b/public/images/stars_lg.png new file mode 100644 index 0000000000000000000000000000000000000000..e8693972a5e37ee53e56bdaafac1777cbf2228a1 GIT binary patch literal 1103 zcmV-V1hD&wP)Zyo0A|m2AVnjs5h`7IEMD$mbw$fxxb~Uow;^uC(?w+fzyQ{c=)?~AY zW;C&hh$BWs#E2szqOb4XpQrD$Jv-fV&-d9qbiCX>&-4C#-rw)%`{ViEJ(&+1bk%|e znHX7v0Wj{Q!45cZDtQBY>gMb@781vY~kU_A%-*lVky1=Q> zW~?SJ139n(JQnmdU|qzNS=##z-IXgVy(8}QM+~mVNrOHx?4-dK`0iBlI$5GutnQ}oz(+cuB^i*^Q3G*SiYl&jd^q$|)b zxrI>7sMUZLv0{T3v_Uc8ZFb(vIr^5+NEnEzXhD~?doCIRss-JQ-E+|<@Efk%K?ZDq z&b<4(cFv`jKoQpga1MGww^&ru`XRy2xyUbi-~%ey0`+-Y$)>($Kt}dlq=He=i{T<` z<^qh9iHyMs*aKb*+D5P=doBu)SaRIPK+D}e7rhCE6j1A=!9&pRq`@cf)v4sQ;0j!W zdM6A9qR}Xi#bO-4$DAkw~+{$cuQ$8p`tXpL0nbI zV7inB*;Fd^#~__flfjYI;0NFr;Dw;UrNl<)o>>gYfll3|9h|DFO2PIKPESr)w|<2I%(!?ul{GpD8j1ewua|V0~_9L9^kx7wl { + $('#prefill-acceptance .text-danger').addClass('hidden') + }) + evt.preventDefault() + } + }) + + //$('#login').modal('show') + $('form[name="prefill-acceptance"]').submit(function(evt) { + let link = $(this).data('link') + let $spinner = $(this).find('.spinner') + $spinner.removeClass('hidden') + $.ajax({ + type: "POST", + url: this.action, + data: $(this).serialize(), // serializes the form's elements. + xhrFields: { + withCredentials: true + }, + success: function(data) { + link += encodeURIComponent(`&applicantDetailsKey=${data.applicantDetailsKey}`) + }, + error: function(err) { + $('#prefill-acceptance .text-danger.hidden').removeClass('hidden') + }, + complete: function() { + $spinner.addClass('hidden') + let win = window.open(link, '_blank') + if (win) { + win.focus(); + } else { + alert('Please allow popups for this website'); + } + } + }); + evt.preventDefault(); + }); +}); diff --git a/routes/index.js b/routes/index.js index 496f633..1e4a0be 100644 --- a/routes/index.js +++ b/routes/index.js @@ -21,8 +21,10 @@ var csrf = require('csurf') var _ = require('lodash') var productViewModel = require('../viewmodels').product -module.exports = function (client) { - var csrfProtection = csrf({ cookie: true }) +module.exports = function(client) { + var csrfProtection = csrf({ + cookie: true + }) var router = express.Router() // The supported card types @@ -46,16 +48,20 @@ module.exports = function (client) { var productCount = 10 /* GET home page. */ - router.get('/', csrfProtection, function (req, res, next) { - var requestedCardType = _.find(cardTypes, { name: req.query.cardType }) + router.get('/', csrfProtection, function(req, res, next) { + var requestedCardType = _.find(cardTypes, { + name: req.query.cardType + }) if (!requestedCardType) { res.redirect('/?cardType=' + cardTypes[0].name) return } - var onComplete = function (err, data) { - if (err) { return next(err) } + var onComplete = function(err, data) { + if (err) { + return next(err) + } cards = _.map(_.get(data, 'products', []), productViewModel) res.render('index', { @@ -63,16 +69,52 @@ module.exports = function (client) { title: 'Credit Offers Reference App', currentCardType: requestedCardType.name, cardTypes: cardTypes, - cards: cards + cards: cards, + user: req.session.user }) } - + if (requestedCardType === allType) { - client.products.getAllCards({ limit: productCount }, onComplete) + client.products.getAllCards({ + limit: productCount + }, onComplete) } else { - client.products.getCards(requestedCardType.name, { limit: productCount }, onComplete) + client.products.getCards(requestedCardType.name, { + limit: productCount + }, onComplete) } }) + router.post('/login', csrfProtection, function(req, res, next) { + let user = require('../creditoffers/users').find((user) => { + return user.username == req.body.username + }) || {} + require('bcrypt').compare(req.body.password, user.password, (err, matched) => { + if (matched) { + req.session.user = user + } + res.redirect('/') + }) + }) + + router.get('/logout', csrfProtection, function(req, res, next) { + req.session = null + res.redirect('/') + }) + + router.post('/prefill-acceptance', csrfProtection, function(req, res, next) { + let user = req.session.user || {} + client.products.postPrefillAcceptance(user.details, (err, response) => { + if (err || !response.applicantDetailsKey) { + res.status(400).send() + // res.json({ + // applicantDetailsKey: "testKey" + // }) + } else { + res.json(response) + } + }) + }) + return router } diff --git a/routes/offers.js b/routes/offers.js index 88baf6a..99bba21 100644 --- a/routes/offers.js +++ b/routes/offers.js @@ -26,14 +26,16 @@ var debug = require('debug')('credit-offers:offers') var productViewModel = require('../viewmodels').preQualProduct var validation = require('../validation') -module.exports = function (client) { +module.exports = function(client) { var router = express.Router() - var csrfProtection = csrf({ cookie: true }) + var csrfProtection = csrf({ + cookie: true + }) // POST customer info to check for offers router.post('/', csrfProtection, - function (req, res, next) { + function(req, res, next) { // Strip out the CSRF token delete req.body._csrf @@ -44,32 +46,40 @@ module.exports = function (client) { var errors = req.validationErrors(true) if (errors) { debug('Validation errors in request body!', util.inspect(errors, false, null)) - var failSummary = _(errors).map(function (error) { - return error.msg - }).value().join('; ') - next(new Error('Validation failed: ' + failSummary)) - return + if (req.xhr) { + return res.status(400).json(errors) + } else { + var failSummary = _(errors).map(function(error) { + return error.msg + }).value().join('; ') + next(new Error('Validation failed: ' + failSummary)) + return + } } // Strip out empty fields - req.body = _.omitBy(req.body, function (value, key) { return value == '' }) + req.body = _.omitBy(req.body, function(value, key) { + return value == '' + }) // Custom body sanitizing req.sanitizeBody('annualIncome').toInt() next() }, - function (req, res, next) { + function(req, res, next) { var customerInfo = getCustomerInfo(req.body) - client.prequalification.create(customerInfo, function (err, response) { - if (err) { return next(err) } + client.prequalification.create(customerInfo, function(err, response) { + if (err) { + return next(err) + } var apiProducts = response.products || [] var productViewModels = _(apiProducts) - .sortBy('priority') // Display in the priority order given by the API - .map(productViewModel) // Transform to a view model for easier display - .value() + .sortBy('priority') // Display in the priority order given by the API + .map(productViewModel) // Transform to a view model for easier display + .value() var viewModel = { title: 'Credit Offers', @@ -114,7 +124,7 @@ module.exports = function (client) { } // POST acknowledgement that prequal offers were displayed - router.post('/acknowledge/:id', function (req, res, next) { + router.post('/acknowledge/:id', function(req, res, next) { debug('Received acknowledgement of ' + req.params.id) var id = req.params.id if (!id) { @@ -122,7 +132,7 @@ module.exports = function (client) { return } - client.prequalification.acknowledge(id, function (err, response) { + client.prequalification.acknowledge(id, function(err, response) { if (err) { debug('Error in API call', err) res.status(500).send() @@ -133,11 +143,11 @@ module.exports = function (client) { }) // GET prequalification summary data for this client - router.get('/summary', function (req, res, next) { + router.get('/summary', function(req, res, next) { debug('Getting prequal summary data') // Get unfiltered summary - client.prequalification.getSummary({}, function (err, response) { + client.prequalification.getSummary({}, function(err, response) { if (err) { debug('Error in API call', err) res.status(500).send() diff --git a/viewmodels/product.js b/viewmodels/product.js index 2ca0b43..2077578 100644 --- a/viewmodels/product.js +++ b/viewmodels/product.js @@ -21,7 +21,7 @@ SPDX-License-Identifier: Apache-2.0 var _ = require('lodash') var sanitize = require('../helpers').sanitize.sanitizeHtmlForDisplay -module.exports = function product (apiProduct) { +module.exports = function product(apiProduct) { var viewModel = _.pick(apiProduct, [ 'productId', 'activeFrom', @@ -56,14 +56,21 @@ module.exports = function product (apiProduct) { 'overLimitFee', 'minimumDeposit', 'additionalMarketingCopy', - 'productCount' + 'productCount', + 'productMetrics' ]) viewModel.productDisplayName = sanitize(apiProduct.productDisplayName || '???') viewModel.images = { - cardName: _.find(apiProduct.images, { imageType: 'CardName' }), - cardArt: _.find(apiProduct.images, { imageType: 'CardArt' }), - banner: _.find(apiProduct.images, { imageType: 'Banner' }) + cardName: _.find(apiProduct.images, { + imageType: 'CardName' + }), + cardArt: _.find(apiProduct.images, { + imageType: 'CardArt' + }), + banner: _.find(apiProduct.images, { + imageType: 'Banner' + }) } var marketingCopy = _.map(apiProduct.marketingCopy || [], sanitize) diff --git a/views/includes/customer-form.jade b/views/includes/customer-form.jade index 1b8320f..643b860 100644 --- a/views/includes/customer-form.jade +++ b/views/includes/customer-form.jade @@ -15,17 +15,24 @@ //- SPDX-License-Identifier: Apache-2.0 //- A re-usable bootstrap text input +- sandboxEnv = this.process.env.NODE_ENV == 'sandbox' mixin textinput(name, label, required) div.form-group(class={required: required}) label(for=name)= label - input.form-control(type='text', name=name, placeholder=attributes.placeholder, required=required || false) + input.form-control(type='text', name=name, placeholder=attributes.placeholder, required=required || false, value=sandboxEnv && attributes.default ? attributes.default : '') mixin select(name, label, required) div.form-group(class={required: required}) label(for=name)= label - select.form-control(name=name) + select.form-control(name=name, selected=attributes.selected || '') block +mixin sandboxTaxIds() + option(value='555555555') 555555555 + option(value='666666666') 666666666 + option(value='777777777') 777777777 + option(value='888888888') 888888888 + mixin stateOptions() option(value='AL') AL option(value='AK') AK @@ -52,7 +59,7 @@ mixin stateOptions() option(value='ME') ME option(value='MH') MH option(value='MD') MD - option(value='MA') MA + option(value='MA')(selected=sandboxEnv ?'selected' : '') MA option(value='MI') MI option(value='MN') MN option(value='MS') MS @@ -91,24 +98,28 @@ mixin stateOptions() option(value='AE') AE option(value='AP') AP -+textinput('firstName', 'First Name', true) ++textinput('firstName', 'First Name', true)(default='Rose') +textinput('middleName', 'Middle Name') -+textinput('lastName', 'Last Name', true) ++textinput('lastName', 'Last Name', true)(default='Dean') +textinput('nameSuffix', 'Suffix')(placeholder='E.g. Jr, Sr') -+textinput('addressLine1', 'Address Line 1', true) ++textinput('addressLine1', 'Address Line 1', true)(default='88 Suffolk St') +textinput('addressLine2', 'Address Line 2') +textinput('addressLine3', 'Address Line 3') +textinput('addressLine4', 'Address Line 4') -+textinput('city', 'City', true) ++textinput('city', 'City', true)(default='Springfield') +select('stateCode', 'State', true) +stateOptions() -+textinput('postalCode', 'Zip Code', true) ++textinput('postalCode', 'Zip Code', true)(default='01109') +select('addressType', 'Address Type') option(value='') option(value='Home') Home option(value='Business') Business -+textinput('taxId', 'Last Four Digits of SSN', true) -+textinput('dateOfBirth', 'Birth Date', false)(placeholder='YYYY-MM-DD') +if sandboxEnv + +select('taxId', 'SSN', true) + +sandboxTaxIds() +else + +textinput('taxId', 'Last Four Digits of SSN', true) ++textinput('dateOfBirth', 'Birth Date', false)(placeholder='YYYY-MM-DD', default='1964-11-12') div.form-note The following information is optional. However, it can help us show you offers that might match your needs and circumstances better. +textinput('emailAddress', 'Email Address') diff --git a/views/includes/prefill-accept-modal.jade b/views/includes/prefill-accept-modal.jade new file mode 100644 index 0000000..d215b8d --- /dev/null +++ b/views/includes/prefill-accept-modal.jade @@ -0,0 +1,43 @@ +//- Copyright 2017 Capital One Services, LLC +//- +//- Licensed under the Apache License, Version 2.0 (the "License"); +//- you may not use this file except in compliance with the License. +//- You may obtain a copy of the License at +//- +//- http://www.apache.org/licenses/LICENSE-2.0 +//- +//- Unless required by applicable law or agreed to in writing, software +//- distributed under the License is distributed on an "AS IS" BASIS, +//- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//- See the License for the specific language governing permissions and limitations under the License. +//- +//- SPDX-Copyright: Copyright (c) Capital One Services, LLC +//- SPDX-License-Identifier: Apache-2.0 +div#prefill-acceptance.modal.fade(tabindex='-1', role='dialog') + div.modal-dialog + div.modal-content + form(action='/prefill-acceptance', method='POST', name="prefill-acceptance") + input(type="hidden" name="_csrf" value="#{csrfToken}") + div.modal-header + button.close(type='button', data-dismiss='modal', aria-label='close') + span(aria-hidden='true') × + h4.modal-title Prefill Acceptance + div.modal-body + | Save time by allowing us to prefill some of your application information. + .bold Information we will attempt to prefill includes: + ul.list-group + li.list-group-item Name (First, Middle, Last & Suffix) + li.list-group-item Address + li.list-group-item Phone Numbers + li.list-group-item Email Addresses + li.list-group-item Income + li.list-group-item Type of Bank Account you may have (if any) + div.modal-footer + span.text-danger.hidden + | Prefill attempt failed + i.spinner.fa.fa-spinner.fa-pulse.fa-fw.hidden +    + button.btn.btn-default(type='button', data-dismiss='modal') + | Close + button.btn.btn-primary(type='submit') + | Accept diff --git a/views/index.jade b/views/index.jade index 1cf028e..0dc18af 100644 --- a/views/index.jade +++ b/views/index.jade @@ -17,14 +17,14 @@ extends ./layout.jade block navbar-custom - .btn-toolbar - button.navbar-right.btn.btn-md.navbar-btn.btn-success(role='button', data-toggle='modal', data-target='#login') - i.btn-img.fa.fa-sign-in(aria-hidden="true") - | Sign In - button.navbar-right.btn.btn-md.navbar-btn.btn-success(role='button', data-toggle='modal', data-target='#customer-info') + .btn-toolbar.navbar-right + button.btn.btn-md.navbar-btn.btn-success(role='button', data-toggle='modal', data-target='#customer-info') i.btn-img.fa.fa-credit-card(aria-hidden="true") | Find Pre-Qualified Offers - + a.btn.btn-md.navbar-btn.btn-success.auth-btn&attributes(user ? { 'class': 'logout-btn', 'href': '/logout' } : { 'data-toggle': 'modal', 'data-target': '#login', 'role': 'button' }) + i.btn-img.fa(aria-hidden="true", class="#{ user ? fa-sign-out : fa-sign-in }") + = user ? 'Sign Out' : 'Sign In' + block content div.header @@ -49,7 +49,18 @@ block content div.main-info div.details p.card-title - !{card.productDisplayName} + span + !{card.productDisplayName} + if card.productMetrics + - metric = card.productMetrics[0].ratingsAndReviews + - review = metric.productReviews + - width = metric.productRating*20 + span.total-stars + span.actual-stars(style='width:#{width}%;') + if review + a.reviews(href="#{review.link}", target="_blank") + = review.count + |  reviews a.json-toggle.pull-right(href="#", title="See raw JSON") {...} div.summary +cardNameImage(card)(data-toggle='tooltip', title='#{(card.productKeywords || []).join(", ")}') @@ -94,3 +105,4 @@ block modals | See Offers include ./includes/raw-json-modal include ./includes/login-modal + include ./includes/prefill-accept-modal diff --git a/views/layout.jade b/views/layout.jade index 90beec0..1e54599 100644 --- a/views/layout.jade +++ b/views/layout.jade @@ -24,7 +24,7 @@ mixin cardNameImage(card) img.card-name(src='/images/default-card.png')&attributes(attributes) mixin applyButton(card) - a.btn.btn.btn-lg.btn-success + a.btn.btn.btn-lg.btn-success.apply-btn(href="#{card.applyNowLink}", target="_blank") i.fa.fa-lock(aria-hidden="true") | APPLY NOW @@ -59,6 +59,7 @@ head link(rel='stylesheet', href='/css/style.css') script(src='https://code.jquery.com/jquery-2.2.0.min.js') script(src='/js/bootstrap.min.js') + script(src='/js/creditoffers.js') body nav.navbar.navbar-default.navbar-fixed-top div.container From 98a0770e40abb7c0468bc0e4f35e514515465c81 Mon Sep 17 00:00:00 2001 From: Hogan Date: Tue, 31 Oct 2017 12:14:39 -0700 Subject: [PATCH 128/147] ajax support for invalid form --- public/js/creditoffers.js | 37 +++++++++++++++++++++++++++++++ views/includes/customer-form.jade | 1 - views/index.jade | 5 ++++- 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/public/js/creditoffers.js b/public/js/creditoffers.js index d3670c3..df2eae8 100644 --- a/public/js/creditoffers.js +++ b/public/js/creditoffers.js @@ -39,4 +39,41 @@ $(function() { }); evt.preventDefault(); }); + + $('form[name="prequalification"]').submit(function(evt) { + $form = $(this) + $.ajax({ + type: "POST", + url: this.action, + data: $(this).serialize(), // serializes the form's elements. + xhrFields: { + withCredentials: true + }, + success: function(data) { + $form.off('submit').submit() + }, + error: function(err) { + if (err.responseJSON) { + $.each($form.serializeArray(), function(i, field) { + $formInputParent = $(`[name="${field.name}"]`).parents('.form-group') + if (err.responseJSON[field.name]) $formInputParent.addClass('has-error') + else $formInputParent.removeClass('has-error') + }) + $form.find('.modal-footer > .text-danger.hidden').removeClass('hidden') + } else { + $form.off('submit').submit() + } + } + // complete: function() { + // $spinner.addClass('hidden') + // let win = window.open(link, '_blank') + // if (win) { + // win.focus(); + // } else { + // alert('Please allow popups for this website'); + // } + // } + }); + evt.preventDefault(); + }); }); diff --git a/views/includes/customer-form.jade b/views/includes/customer-form.jade index 643b860..c63f4a4 100644 --- a/views/includes/customer-form.jade +++ b/views/includes/customer-form.jade @@ -20,7 +20,6 @@ mixin textinput(name, label, required) div.form-group(class={required: required}) label(for=name)= label input.form-control(type='text', name=name, placeholder=attributes.placeholder, required=required || false, value=sandboxEnv && attributes.default ? attributes.default : '') - mixin select(name, label, required) div.form-group(class={required: required}) label(for=name)= label diff --git a/views/index.jade b/views/index.jade index 0dc18af..7cdb9e7 100644 --- a/views/index.jade +++ b/views/index.jade @@ -90,7 +90,7 @@ block modals div#customer-info.modal.fade(tabindex='-1', role='dialog') div.modal-dialog div.modal-content - form(action='/offers', method='POST') + form(action='/offers', method='POST', name="prequalification") input(type="hidden" name="_csrf" value="#{csrfToken}") div.modal-header button.close(type='button', data-dismiss='modal', aria-label='close') @@ -99,6 +99,9 @@ block modals div.modal-body include ./includes/customer-form div.modal-footer + span.text-danger.hidden + | Errors detected. +    button.btn.btn-default(type='button', data-dismiss='modal') | Close button.btn.btn-primary(type='submit') From 645b79d52b5ba4f8cc2772fa50587cfd8f9567e6 Mon Sep 17 00:00:00 2001 From: Hogan Date: Thu, 2 Nov 2017 15:29:18 -0700 Subject: [PATCH 129/147] cleanup and pq ajax removal --- public/js/creditoffers.js | 63 ++++++++++-------------- views/includes/customer-form.jade | 21 ++++---- views/includes/login-modal.jade | 2 +- views/includes/prefill-accept-modal.jade | 2 +- views/index.jade | 15 +++--- 5 files changed, 47 insertions(+), 56 deletions(-) diff --git a/public/js/creditoffers.js b/public/js/creditoffers.js index df2eae8..cb2d650 100644 --- a/public/js/creditoffers.js +++ b/public/js/creditoffers.js @@ -40,40 +40,31 @@ $(function() { evt.preventDefault(); }); - $('form[name="prequalification"]').submit(function(evt) { - $form = $(this) - $.ajax({ - type: "POST", - url: this.action, - data: $(this).serialize(), // serializes the form's elements. - xhrFields: { - withCredentials: true - }, - success: function(data) { - $form.off('submit').submit() - }, - error: function(err) { - if (err.responseJSON) { - $.each($form.serializeArray(), function(i, field) { - $formInputParent = $(`[name="${field.name}"]`).parents('.form-group') - if (err.responseJSON[field.name]) $formInputParent.addClass('has-error') - else $formInputParent.removeClass('has-error') - }) - $form.find('.modal-footer > .text-danger.hidden').removeClass('hidden') - } else { - $form.off('submit').submit() - } - } - // complete: function() { - // $spinner.addClass('hidden') - // let win = window.open(link, '_blank') - // if (win) { - // win.focus(); - // } else { - // alert('Please allow popups for this website'); - // } - // } - }); - evt.preventDefault(); - }); + // $('form[name="prequalification"]').submit(function(evt) { + // evt.preventDefault(); + // $form = $(this) + // $.ajax({ + // type: "POST", + // url: this.action, + // data: $(this).serialize(), // serializes the form's elements. + // xhrFields: { + // withCredentials: true + // }, + // success: function(data) { + // $form[0].submit() + // }, + // error: function(err) { + // if (err.responseJSON) { + // $.each($form.serializeArray(), function(i, field) { + // $formInputParent = $(`[name="${field.name}"]`).parents('.form-group') + // if (err.responseJSON[field.name]) $formInputParent.addClass('has-error') + // else $formInputParent.removeClass('has-error') + // }) + // $form.find('.modal-footer > .text-danger.hidden').removeClass('hidden') + // } else { + // $form[0].submit() + // } + // } + // }); + // }); }); diff --git a/views/includes/customer-form.jade b/views/includes/customer-form.jade index c63f4a4..af0449f 100644 --- a/views/includes/customer-form.jade +++ b/views/includes/customer-form.jade @@ -15,7 +15,7 @@ //- SPDX-License-Identifier: Apache-2.0 //- A re-usable bootstrap text input -- sandboxEnv = this.process.env.NODE_ENV == 'sandbox' +- sandboxEnv = this.process.env.C1_ENV == 'sandbox' mixin textinput(name, label, required) div.form-group(class={required: required}) label(for=name)= label @@ -26,11 +26,11 @@ mixin select(name, label, required) select.form-control(name=name, selected=attributes.selected || '') block -mixin sandboxTaxIds() - option(value='555555555') 555555555 - option(value='666666666') 666666666 - option(value='777777777') 777777777 - option(value='888888888') 888888888 +mixin taxIdOptions() + option(value='555555555') 555555555 (No Prequalification Offer) + option(value='666666666') 666666666 (Single Prequalification Offer) + option(value='777777777') 777777777 (Multiple Prequalified Offers) + option(value='888888888') 888888888 (Max Prequalification Offers) mixin stateOptions() option(value='AL') AL @@ -58,7 +58,10 @@ mixin stateOptions() option(value='ME') ME option(value='MH') MH option(value='MD') MD - option(value='MA')(selected=sandboxEnv ?'selected' : '') MA + if sandboxEnv + option(value='MA', selected='selected') MA + else + option(value='MA') MA option(value='MI') MI option(value='MN') MN option(value='MS') MS @@ -115,8 +118,8 @@ mixin stateOptions() option(value='Business') Business if sandboxEnv +select('taxId', 'SSN', true) - +sandboxTaxIds() -else + +taxIdOptions() +else +textinput('taxId', 'Last Four Digits of SSN', true) +textinput('dateOfBirth', 'Birth Date', false)(placeholder='YYYY-MM-DD', default='1964-11-12') diff --git a/views/includes/login-modal.jade b/views/includes/login-modal.jade index 47a168f..8b75134 100644 --- a/views/includes/login-modal.jade +++ b/views/includes/login-modal.jade @@ -13,7 +13,7 @@ //- //- SPDX-Copyright: Copyright (c) Capital One Services, LLC //- SPDX-License-Identifier: Apache-2.0 -- sandboxEnv = this.process.env.NODE_ENV == 'sandbox' +- sandboxEnv = this.process.env.C1_ENV == 'sandbox' div#login.modal.fade(tabindex='-1', role='dialog') div.modal-dialog div.modal-content diff --git a/views/includes/prefill-accept-modal.jade b/views/includes/prefill-accept-modal.jade index d215b8d..8dec316 100644 --- a/views/includes/prefill-accept-modal.jade +++ b/views/includes/prefill-accept-modal.jade @@ -36,7 +36,7 @@ div#prefill-acceptance.modal.fade(tabindex='-1', role='dialog') span.text-danger.hidden | Prefill attempt failed i.spinner.fa.fa-spinner.fa-pulse.fa-fw.hidden -    + |    button.btn.btn-default(type='button', data-dismiss='modal') | Close button.btn.btn-primary(type='submit') diff --git a/views/index.jade b/views/index.jade index 7cdb9e7..8526d7c 100644 --- a/views/index.jade +++ b/views/index.jade @@ -50,22 +50,19 @@ block content div.details p.card-title span - !{card.productDisplayName} - if card.productMetrics + | !{card.productDisplayName} + if card.productMetrics && card.productMetrics.length - metric = card.productMetrics[0].ratingsAndReviews - review = metric.productReviews - - width = metric.productRating*20 span.total-stars - span.actual-stars(style='width:#{width}%;') + span.actual-stars(style='width:#{metric.productRating*20}%;') if review - a.reviews(href="#{review.link}", target="_blank") - = review.count - |  reviews + a.reviews(href="#{review.link}", target="_blank") !{review.count} reviews a.json-toggle.pull-right(href="#", title="See raw JSON") {...} div.summary +cardNameImage(card)(data-toggle='tooltip', title='#{(card.productKeywords || []).join(", ")}') +marketingCopy(card) - div.apply.json-toggle.pull-right(title="Apply Now Link Disabled in Sandbox") + div.apply.json-toggle.pull-right +applyButton(card) p with CapitalOne sup ® @@ -101,7 +98,7 @@ block modals div.modal-footer span.text-danger.hidden | Errors detected. -    + |    button.btn.btn-default(type='button', data-dismiss='modal') | Close button.btn.btn-primary(type='submit') From 1a1e31efdffb3b83c8521e646bc212f7e1881697 Mon Sep 17 00:00:00 2001 From: Hogan Date: Mon, 6 Nov 2017 10:32:39 -0800 Subject: [PATCH 130/147] test on heroku --- app.js | 18 ++++-- creditoffers/products.js | 31 +++++++--- creditoffers/users.js | 60 +++++++++++++++++++ generateUsers.js | 10 ++++ package.json | 2 + public/css/style.css | 23 ++++++++ public/images/stars_lg.png | Bin 0 -> 1103 bytes public/js/creditoffers.js | 71 +++++++++++++++++++++++ routes/index.js | 62 ++++++++++++++++---- routes/offers.js | 48 +++++++++------ viewmodels/product.js | 17 ++++-- views/includes/customer-form.jade | 35 +++++++---- views/includes/login-modal.jade | 37 ++++++++++++ views/includes/prefill-accept-modal.jade | 43 ++++++++++++++ views/index.jade | 31 +++++++--- views/layout.jade | 3 +- 16 files changed, 426 insertions(+), 65 deletions(-) create mode 100644 creditoffers/users.js create mode 100644 generateUsers.js create mode 100644 public/images/stars_lg.png create mode 100644 public/js/creditoffers.js create mode 100644 views/includes/login-modal.jade create mode 100644 views/includes/prefill-accept-modal.jade diff --git a/app.js b/app.js index e8771f5..25b8c70 100644 --- a/app.js +++ b/app.js @@ -21,6 +21,7 @@ var path = require('path') var favicon = require('serve-favicon') var logger = require('morgan') var cookieParser = require('cookie-parser') +var cookieSession = require('cookie-session') var bodyParser = require('body-parser') var expressValidator = require('express-validator') var helmet = require('helmet') @@ -44,11 +45,20 @@ app.set('view engine', 'jade') app.use(favicon(path.join(__dirname, 'public', 'favicon.ico'))) app.use(logger('dev')) app.use(bodyParser.json()) -app.use(bodyParser.urlencoded({ extended: false })) +app.use(bodyParser.urlencoded({ + extended: false +})) app.use(expressValidator({ customValidators: validation.customValidators })) app.use(cookieParser()) +app.use(cookieSession({ + name: 'session', + secret: "b34ejefAt7a3eme5e7paCrEgatadEqEs", + + // Cookie Options + maxAge: 24 * 60 * 60 * 1000 // 24 hours +})) app.use(express.static(path.join(__dirname, 'public'))) // Include the bootstrap package app.use('/css', express.static(path.join(__dirname, 'node_modules/bootstrap/dist/css'))) @@ -65,7 +75,7 @@ app.use('/', index(client)) app.use('/offers', offers(client)) // catch 404 and forward to error handler -app.use(function (req, res, next) { +app.use(function(req, res, next) { var err = new Error('Not Found') err.status = 404 next(err) @@ -76,7 +86,7 @@ app.use(function (req, res, next) { // development error handler // will print stacktrace if (app.get('env') === 'development') { - app.use(function (err, req, res, next) { + app.use(function(err, req, res, next) { res.status(err.status || 500) res.render('error', { message: err.message, @@ -87,7 +97,7 @@ if (app.get('env') === 'development') { // production error handler // no stacktraces leaked to user -app.use(function (err, req, res, next) { +app.use(function(err, req, res, next) { res.status(err.status || 500) res.render('error', { message: err.message, diff --git a/creditoffers/products.js b/creditoffers/products.js index 7666dc6..341f235 100644 --- a/creditoffers/products.js +++ b/creditoffers/products.js @@ -24,7 +24,7 @@ var _ = require('lodash') * Contains all functions for interacting with the credit offer product listings API * @param {object} client The API client */ -function Products (client) { +function Products(client) { if (!this instanceof Products) { return new Products(client) } @@ -38,7 +38,7 @@ module.exports = Products * @param {object} pagingOptions Optionally control the number of results and starting offset * in the result set */ -Products.prototype.getAll = function getAll (pagingOptions, callback) { +Products.prototype.getAll = function getAll(pagingOptions, callback) { var query = _.pick(pagingOptions, ['limit', 'offset']) this.client.sendRequest({ @@ -54,7 +54,7 @@ Products.prototype.getAll = function getAll (pagingOptions, callback) { * @param {object} pagingOptions Optionally control the number of results and starting offset * in the result set */ -Products.prototype.getAllCards = function getAllCards (pagingOptions, callback) { +Products.prototype.getAllCards = function getAllCards(pagingOptions, callback) { var query = _.pick(pagingOptions, ['limit', 'offset']) this.client.sendRequest({ @@ -71,8 +71,10 @@ Products.prototype.getAllCards = function getAllCards (pagingOptions, callback) * @param {object} pagingOptions Optionally control the number of results and starting offset * in the result set */ -Products.prototype.getCards = function getCards (cardType, pagingOptions, callback) { - if (!cardType) { callback(new Error('A card type must be specified')) } +Products.prototype.getCards = function getCards(cardType, pagingOptions, callback) { + if (!cardType) { + callback(new Error('A card type must be specified')) + } var query = _.pick(pagingOptions, ['limit', 'offset']) this.client.sendRequest({ @@ -88,9 +90,13 @@ Products.prototype.getCards = function getCards (cardType, pagingOptions, callba * @param {string} cardType The type of card (BusinessCard, ConsumerCard) * @param {string} productId The ID of the consumer card product for which to retrieve details */ -Products.prototype.getCardDetail = function getCardDetail (cardType, productId, callback) { - if (!cardType) { callback(new Error('A card type must be specified')) } - if (!productId) { callback(new Error('A product ID must be specified in order to retrieve product details')) } +Products.prototype.getCardDetail = function getCardDetail(cardType, productId, callback) { + if (!cardType) { + callback(new Error('A card type must be specified')) + } + if (!productId) { + callback(new Error('A product ID must be specified in order to retrieve product details')) + } this.client.sendRequest({ url: '/credit-offers/products/' + encodeURIComponent(cardType) + '/' + encodeURIComponent(productId), @@ -98,3 +104,12 @@ Products.prototype.getCardDetail = function getCardDetail (cardType, productId, method: 'GET' }, callback) } + +Products.prototype.postPrefillAcceptance = function postPrefillAcceptance(user, callback) { + this.client.sendRequest({ + url: '/credit-offers/applicant-details', + useOAuth: true, + method: 'POST', + body: user + }, callback) +} diff --git a/creditoffers/users.js b/creditoffers/users.js new file mode 100644 index 0000000..3d446ac --- /dev/null +++ b/creditoffers/users.js @@ -0,0 +1,60 @@ +module.exports = [{ + username: "rhubbard", + password: "$2a$10$FNWEnW7IR5pusrCtkDpdfeDqhvkGexDOJ3GB9Ae08yHgJGsw0jXIG", + details: { + "firstName": "Ray", + "middleName": "G", + "lastName": "Hubbard", + "nameSuffix": "Jr.", + "addresses": [{ + "addressLine1": "1230 Duck Fuss Lane", + "addressLine2": "Apt 15X", + "addressLine3": "Room 192", + "addressLine4": "Bed 16", + "city": "Beaumont", + "stateCode": "TX", + "postalCode": "77701", + "addressType": "PhysicalPrimary" + + }], + "telephoneNumbers": [{ + "phoneNumberType": "Home", + "telephoneNumber": "2025550110" + }], + "emailAddresses": [{ + "emailAddress": "ray@wyliehubbard.com" + }], + "annualIncome": 75000, + "bankAccountSummary": "CheckingOnly" + } + }, + { + username: "jsmith", + password: "$2a$10$KVkQGwZ0qNxau0tHBpg16O5rym/g3dfKJEd5kCnQ4nOJn1oOjzKca", + details: { + "firstName": "Jane", + "lastName": "Smith", + "addresses": [{ + "addressLine1": "3828 Piermont Dr", + "addressLine2": "Unit A", + "city": "Albuquerque", + "stateCode": "NM", + "postalCode": "87111", + "addressType": "Mailing" + + }], + "emailAddresses": [{ + "emailAddress": "jane@example.com" + }], + "telephoneNumbers": [{ + "phoneNumberType": "Work", + "telephoneNumber": "2025550246" + }, { + "phoneNumberType": "Mobile", + "telephoneNumber": "2025551357" + }], + "annualIncome": 100000, + "bankAccountSummary": "Neither" + } + } +] diff --git a/generateUsers.js b/generateUsers.js new file mode 100644 index 0000000..338fd59 --- /dev/null +++ b/generateUsers.js @@ -0,0 +1,10 @@ +var bcrypt = require('bcrypt') +const saltRounds = 10; + +bcrypt.hash('Reference@123', saltRounds, function(err, salt) { + console.log('salt', salt) +}) + +bcrypt.hash('Reference@123', saltRounds, function(err, salt) { + console.log('salt', salt) +}) diff --git a/package.json b/package.json index 3681af6..40a4add 100644 --- a/package.json +++ b/package.json @@ -9,9 +9,11 @@ } ], "dependencies": { + "bcrypt": "^1.0.3", "body-parser": "1.13.3", "bootstrap": "3.3.7", "cookie-parser": "1.3.5", + "cookie-session": "^2.0.0-beta.3", "csurf": "1.8.3", "debug": "2.2.0", "ejs": "2.3.4", diff --git a/public/css/style.css b/public/css/style.css index 391367a..3a3ac7e 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -38,6 +38,11 @@ body { border-bottom: 1px solid #ccc; } +.bold { + font-weight: bold; + padding: 5px 0; +} + .header { background-color: #efefef; padding-bottom: 10px; } @@ -57,6 +62,24 @@ body { .card-info .card-title { font-size: 1.5em; font-weight: 600; } + .card-info .total-stars { + width: 80px; + height: 16px; + display: inline-block; + background-size: 16px 48px; + margin: 0 5px; + background-image: url('/images/stars_lg.png'); + background-position: 0 -16px; + } + .card-info .actual-stars { + height: 16px; + display: inline-block; + background-size: 16px 48px; + background-image: url('/images/stars_lg.png'); + } + .card-info a.reviews { + font-size: 12px; + } .card-info .main-info { display: flex; flex-flow: row nowrap; } diff --git a/public/images/stars_lg.png b/public/images/stars_lg.png new file mode 100644 index 0000000000000000000000000000000000000000..e8693972a5e37ee53e56bdaafac1777cbf2228a1 GIT binary patch literal 1103 zcmV-V1hD&wP)Zyo0A|m2AVnjs5h`7IEMD$mbw$fxxb~Uow;^uC(?w+fzyQ{c=)?~AY zW;C&hh$BWs#E2szqOb4XpQrD$Jv-fV&-d9qbiCX>&-4C#-rw)%`{ViEJ(&+1bk%|e znHX7v0Wj{Q!45cZDtQBY>gMb@781vY~kU_A%-*lVky1=Q> zW~?SJ139n(JQnmdU|qzNS=##z-IXgVy(8}QM+~mVNrOHx?4-dK`0iBlI$5GutnQ}oz(+cuB^i*^Q3G*SiYl&jd^q$|)b zxrI>7sMUZLv0{T3v_Uc8ZFb(vIr^5+NEnEzXhD~?doCIRss-JQ-E+|<@Efk%K?ZDq z&b<4(cFv`jKoQpga1MGww^&ru`XRy2xyUbi-~%ey0`+-Y$)>($Kt}dlq=He=i{T<` z<^qh9iHyMs*aKb*+D5P=doBu)SaRIPK+D}e7rhCE6j1A=!9&pRq`@cf)v4sQ;0j!W zdM6A9qR}Xi#bO-4$DAkw~+{$cuQ$8p`tXpL0nbI zV7inB*;Fd^#~__flfjYI;0NFr;Dw;UrNl<)o>>gYfll3|9h|DFO2PIKPESr)w|<2I%(!?ul{GpD8j1ewua|V0~_9L9^kx7wl { + $('#prefill-acceptance .text-danger').addClass('hidden') + }) + evt.preventDefault() + } + }) + + + //$('#login').modal('show') + $('form[name="prefill-acceptance"]').submit(function(evt) { + let link = $(this).data('link') + let $spinner = $(this).find('.spinner') + $spinner.removeClass('hidden') + $.ajax({ + type: "POST", + url: this.action, + data: $(this).serialize(), // serializes the form's elements. + xhrFields: { + withCredentials: true + }, + success: function(data) { + link += encodeURIComponent(`&applicantDetailsKey=${data.applicantDetailsKey}`) + }, + error: function(err) { + $('#prefill-acceptance .text-danger.hidden').removeClass('hidden') + }, + complete: function() { + $spinner.addClass('hidden') + let win = window.open(link, '_blank') + if (win) { + win.focus(); + } else { + alert('Please allow popups for this website'); + } + } + }); + evt.preventDefault(); + }); + + // $('form[name="prequalification"]').submit(function(evt) { + // evt.preventDefault(); + // $form = $(this) + // $.ajax({ + // type: "POST", + // url: this.action, + // data: $(this).serialize(), // serializes the form's elements. + // xhrFields: { + // withCredentials: true + // }, + // success: function(data) { + // $form[0].submit() + // }, + // error: function(err) { + // if (err.responseJSON) { + // $.each($form.serializeArray(), function(i, field) { + // $formInputParent = $(`[name="${field.name}"]`).parents('.form-group') + // if (err.responseJSON[field.name]) $formInputParent.addClass('has-error') + // else $formInputParent.removeClass('has-error') + // }) + // $form.find('.modal-footer > .text-danger.hidden').removeClass('hidden') + // } else { + // $form[0].submit() + // } + // } + // }); + // }); +}); diff --git a/routes/index.js b/routes/index.js index 496f633..1e4a0be 100644 --- a/routes/index.js +++ b/routes/index.js @@ -21,8 +21,10 @@ var csrf = require('csurf') var _ = require('lodash') var productViewModel = require('../viewmodels').product -module.exports = function (client) { - var csrfProtection = csrf({ cookie: true }) +module.exports = function(client) { + var csrfProtection = csrf({ + cookie: true + }) var router = express.Router() // The supported card types @@ -46,16 +48,20 @@ module.exports = function (client) { var productCount = 10 /* GET home page. */ - router.get('/', csrfProtection, function (req, res, next) { - var requestedCardType = _.find(cardTypes, { name: req.query.cardType }) + router.get('/', csrfProtection, function(req, res, next) { + var requestedCardType = _.find(cardTypes, { + name: req.query.cardType + }) if (!requestedCardType) { res.redirect('/?cardType=' + cardTypes[0].name) return } - var onComplete = function (err, data) { - if (err) { return next(err) } + var onComplete = function(err, data) { + if (err) { + return next(err) + } cards = _.map(_.get(data, 'products', []), productViewModel) res.render('index', { @@ -63,16 +69,52 @@ module.exports = function (client) { title: 'Credit Offers Reference App', currentCardType: requestedCardType.name, cardTypes: cardTypes, - cards: cards + cards: cards, + user: req.session.user }) } - + if (requestedCardType === allType) { - client.products.getAllCards({ limit: productCount }, onComplete) + client.products.getAllCards({ + limit: productCount + }, onComplete) } else { - client.products.getCards(requestedCardType.name, { limit: productCount }, onComplete) + client.products.getCards(requestedCardType.name, { + limit: productCount + }, onComplete) } }) + router.post('/login', csrfProtection, function(req, res, next) { + let user = require('../creditoffers/users').find((user) => { + return user.username == req.body.username + }) || {} + require('bcrypt').compare(req.body.password, user.password, (err, matched) => { + if (matched) { + req.session.user = user + } + res.redirect('/') + }) + }) + + router.get('/logout', csrfProtection, function(req, res, next) { + req.session = null + res.redirect('/') + }) + + router.post('/prefill-acceptance', csrfProtection, function(req, res, next) { + let user = req.session.user || {} + client.products.postPrefillAcceptance(user.details, (err, response) => { + if (err || !response.applicantDetailsKey) { + res.status(400).send() + // res.json({ + // applicantDetailsKey: "testKey" + // }) + } else { + res.json(response) + } + }) + }) + return router } diff --git a/routes/offers.js b/routes/offers.js index 88baf6a..99bba21 100644 --- a/routes/offers.js +++ b/routes/offers.js @@ -26,14 +26,16 @@ var debug = require('debug')('credit-offers:offers') var productViewModel = require('../viewmodels').preQualProduct var validation = require('../validation') -module.exports = function (client) { +module.exports = function(client) { var router = express.Router() - var csrfProtection = csrf({ cookie: true }) + var csrfProtection = csrf({ + cookie: true + }) // POST customer info to check for offers router.post('/', csrfProtection, - function (req, res, next) { + function(req, res, next) { // Strip out the CSRF token delete req.body._csrf @@ -44,32 +46,40 @@ module.exports = function (client) { var errors = req.validationErrors(true) if (errors) { debug('Validation errors in request body!', util.inspect(errors, false, null)) - var failSummary = _(errors).map(function (error) { - return error.msg - }).value().join('; ') - next(new Error('Validation failed: ' + failSummary)) - return + if (req.xhr) { + return res.status(400).json(errors) + } else { + var failSummary = _(errors).map(function(error) { + return error.msg + }).value().join('; ') + next(new Error('Validation failed: ' + failSummary)) + return + } } // Strip out empty fields - req.body = _.omitBy(req.body, function (value, key) { return value == '' }) + req.body = _.omitBy(req.body, function(value, key) { + return value == '' + }) // Custom body sanitizing req.sanitizeBody('annualIncome').toInt() next() }, - function (req, res, next) { + function(req, res, next) { var customerInfo = getCustomerInfo(req.body) - client.prequalification.create(customerInfo, function (err, response) { - if (err) { return next(err) } + client.prequalification.create(customerInfo, function(err, response) { + if (err) { + return next(err) + } var apiProducts = response.products || [] var productViewModels = _(apiProducts) - .sortBy('priority') // Display in the priority order given by the API - .map(productViewModel) // Transform to a view model for easier display - .value() + .sortBy('priority') // Display in the priority order given by the API + .map(productViewModel) // Transform to a view model for easier display + .value() var viewModel = { title: 'Credit Offers', @@ -114,7 +124,7 @@ module.exports = function (client) { } // POST acknowledgement that prequal offers were displayed - router.post('/acknowledge/:id', function (req, res, next) { + router.post('/acknowledge/:id', function(req, res, next) { debug('Received acknowledgement of ' + req.params.id) var id = req.params.id if (!id) { @@ -122,7 +132,7 @@ module.exports = function (client) { return } - client.prequalification.acknowledge(id, function (err, response) { + client.prequalification.acknowledge(id, function(err, response) { if (err) { debug('Error in API call', err) res.status(500).send() @@ -133,11 +143,11 @@ module.exports = function (client) { }) // GET prequalification summary data for this client - router.get('/summary', function (req, res, next) { + router.get('/summary', function(req, res, next) { debug('Getting prequal summary data') // Get unfiltered summary - client.prequalification.getSummary({}, function (err, response) { + client.prequalification.getSummary({}, function(err, response) { if (err) { debug('Error in API call', err) res.status(500).send() diff --git a/viewmodels/product.js b/viewmodels/product.js index 2ca0b43..2077578 100644 --- a/viewmodels/product.js +++ b/viewmodels/product.js @@ -21,7 +21,7 @@ SPDX-License-Identifier: Apache-2.0 var _ = require('lodash') var sanitize = require('../helpers').sanitize.sanitizeHtmlForDisplay -module.exports = function product (apiProduct) { +module.exports = function product(apiProduct) { var viewModel = _.pick(apiProduct, [ 'productId', 'activeFrom', @@ -56,14 +56,21 @@ module.exports = function product (apiProduct) { 'overLimitFee', 'minimumDeposit', 'additionalMarketingCopy', - 'productCount' + 'productCount', + 'productMetrics' ]) viewModel.productDisplayName = sanitize(apiProduct.productDisplayName || '???') viewModel.images = { - cardName: _.find(apiProduct.images, { imageType: 'CardName' }), - cardArt: _.find(apiProduct.images, { imageType: 'CardArt' }), - banner: _.find(apiProduct.images, { imageType: 'Banner' }) + cardName: _.find(apiProduct.images, { + imageType: 'CardName' + }), + cardArt: _.find(apiProduct.images, { + imageType: 'CardArt' + }), + banner: _.find(apiProduct.images, { + imageType: 'Banner' + }) } var marketingCopy = _.map(apiProduct.marketingCopy || [], sanitize) diff --git a/views/includes/customer-form.jade b/views/includes/customer-form.jade index 1b8320f..af0449f 100644 --- a/views/includes/customer-form.jade +++ b/views/includes/customer-form.jade @@ -15,17 +15,23 @@ //- SPDX-License-Identifier: Apache-2.0 //- A re-usable bootstrap text input +- sandboxEnv = this.process.env.C1_ENV == 'sandbox' mixin textinput(name, label, required) div.form-group(class={required: required}) label(for=name)= label - input.form-control(type='text', name=name, placeholder=attributes.placeholder, required=required || false) - + input.form-control(type='text', name=name, placeholder=attributes.placeholder, required=required || false, value=sandboxEnv && attributes.default ? attributes.default : '') mixin select(name, label, required) div.form-group(class={required: required}) label(for=name)= label - select.form-control(name=name) + select.form-control(name=name, selected=attributes.selected || '') block +mixin taxIdOptions() + option(value='555555555') 555555555 (No Prequalification Offer) + option(value='666666666') 666666666 (Single Prequalification Offer) + option(value='777777777') 777777777 (Multiple Prequalified Offers) + option(value='888888888') 888888888 (Max Prequalification Offers) + mixin stateOptions() option(value='AL') AL option(value='AK') AK @@ -52,7 +58,10 @@ mixin stateOptions() option(value='ME') ME option(value='MH') MH option(value='MD') MD - option(value='MA') MA + if sandboxEnv + option(value='MA', selected='selected') MA + else + option(value='MA') MA option(value='MI') MI option(value='MN') MN option(value='MS') MS @@ -91,24 +100,28 @@ mixin stateOptions() option(value='AE') AE option(value='AP') AP -+textinput('firstName', 'First Name', true) ++textinput('firstName', 'First Name', true)(default='Rose') +textinput('middleName', 'Middle Name') -+textinput('lastName', 'Last Name', true) ++textinput('lastName', 'Last Name', true)(default='Dean') +textinput('nameSuffix', 'Suffix')(placeholder='E.g. Jr, Sr') -+textinput('addressLine1', 'Address Line 1', true) ++textinput('addressLine1', 'Address Line 1', true)(default='88 Suffolk St') +textinput('addressLine2', 'Address Line 2') +textinput('addressLine3', 'Address Line 3') +textinput('addressLine4', 'Address Line 4') -+textinput('city', 'City', true) ++textinput('city', 'City', true)(default='Springfield') +select('stateCode', 'State', true) +stateOptions() -+textinput('postalCode', 'Zip Code', true) ++textinput('postalCode', 'Zip Code', true)(default='01109') +select('addressType', 'Address Type') option(value='') option(value='Home') Home option(value='Business') Business -+textinput('taxId', 'Last Four Digits of SSN', true) -+textinput('dateOfBirth', 'Birth Date', false)(placeholder='YYYY-MM-DD') +if sandboxEnv + +select('taxId', 'SSN', true) + +taxIdOptions() +else + +textinput('taxId', 'Last Four Digits of SSN', true) ++textinput('dateOfBirth', 'Birth Date', false)(placeholder='YYYY-MM-DD', default='1964-11-12') div.form-note The following information is optional. However, it can help us show you offers that might match your needs and circumstances better. +textinput('emailAddress', 'Email Address') diff --git a/views/includes/login-modal.jade b/views/includes/login-modal.jade new file mode 100644 index 0000000..8b75134 --- /dev/null +++ b/views/includes/login-modal.jade @@ -0,0 +1,37 @@ +//- Copyright 2017 Capital One Services, LLC +//- +//- Licensed under the Apache License, Version 2.0 (the "License"); +//- you may not use this file except in compliance with the License. +//- You may obtain a copy of the License at +//- +//- http://www.apache.org/licenses/LICENSE-2.0 +//- +//- Unless required by applicable law or agreed to in writing, software +//- distributed under the License is distributed on an "AS IS" BASIS, +//- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//- See the License for the specific language governing permissions and limitations under the License. +//- +//- SPDX-Copyright: Copyright (c) Capital One Services, LLC +//- SPDX-License-Identifier: Apache-2.0 +- sandboxEnv = this.process.env.C1_ENV == 'sandbox' +div#login.modal.fade(tabindex='-1', role='dialog') + div.modal-dialog + div.modal-content + form(action='/login', method='POST', name="login") + input(type="hidden" name="_csrf" value="#{csrfToken}") + div.modal-header + button.close(type='button', data-dismiss='modal', aria-label='close') + span(aria-hidden='true') × + h4.modal-title Sign In + div.modal-body + div.form-group(class={required: truth}) + label(for='username')= 'Username' + input.form-control(type='text', name='username', required='true', value=sandboxEnv ? 'jsmith' : '') + div.form-group(class={required: truth}) + label(for='password')= 'Password' + input.form-control(type='password', name='password', required='true', value=sandboxEnv ? 'Reference@123' : '') + div.modal-footer + button.btn.btn-default(type='button', data-dismiss='modal') + | Close + button.btn.btn-primary(type='submit') + | Sign In diff --git a/views/includes/prefill-accept-modal.jade b/views/includes/prefill-accept-modal.jade new file mode 100644 index 0000000..8dec316 --- /dev/null +++ b/views/includes/prefill-accept-modal.jade @@ -0,0 +1,43 @@ +//- Copyright 2017 Capital One Services, LLC +//- +//- Licensed under the Apache License, Version 2.0 (the "License"); +//- you may not use this file except in compliance with the License. +//- You may obtain a copy of the License at +//- +//- http://www.apache.org/licenses/LICENSE-2.0 +//- +//- Unless required by applicable law or agreed to in writing, software +//- distributed under the License is distributed on an "AS IS" BASIS, +//- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//- See the License for the specific language governing permissions and limitations under the License. +//- +//- SPDX-Copyright: Copyright (c) Capital One Services, LLC +//- SPDX-License-Identifier: Apache-2.0 +div#prefill-acceptance.modal.fade(tabindex='-1', role='dialog') + div.modal-dialog + div.modal-content + form(action='/prefill-acceptance', method='POST', name="prefill-acceptance") + input(type="hidden" name="_csrf" value="#{csrfToken}") + div.modal-header + button.close(type='button', data-dismiss='modal', aria-label='close') + span(aria-hidden='true') × + h4.modal-title Prefill Acceptance + div.modal-body + | Save time by allowing us to prefill some of your application information. + .bold Information we will attempt to prefill includes: + ul.list-group + li.list-group-item Name (First, Middle, Last & Suffix) + li.list-group-item Address + li.list-group-item Phone Numbers + li.list-group-item Email Addresses + li.list-group-item Income + li.list-group-item Type of Bank Account you may have (if any) + div.modal-footer + span.text-danger.hidden + | Prefill attempt failed + i.spinner.fa.fa-spinner.fa-pulse.fa-fw.hidden + |    + button.btn.btn-default(type='button', data-dismiss='modal') + | Close + button.btn.btn-primary(type='submit') + | Accept diff --git a/views/index.jade b/views/index.jade index 5eb2f8e..8526d7c 100644 --- a/views/index.jade +++ b/views/index.jade @@ -17,9 +17,14 @@ extends ./layout.jade block navbar-custom - button.navbar-right.btn.btn-md.navbar-btn.btn-success(role='button', data-toggle='modal', data-target='#customer-info') - i.btn-img.fa.fa-credit-card(aria-hidden="true") - | Find Pre-Qualified Offers + .btn-toolbar.navbar-right + button.btn.btn-md.navbar-btn.btn-success(role='button', data-toggle='modal', data-target='#customer-info') + i.btn-img.fa.fa-credit-card(aria-hidden="true") + | Find Pre-Qualified Offers + a.btn.btn-md.navbar-btn.btn-success.auth-btn&attributes(user ? { 'class': 'logout-btn', 'href': '/logout' } : { 'data-toggle': 'modal', 'data-target': '#login', 'role': 'button' }) + i.btn-img.fa(aria-hidden="true", class="#{ user ? fa-sign-out : fa-sign-in }") + = user ? 'Sign Out' : 'Sign In' + block content div.header @@ -44,12 +49,20 @@ block content div.main-info div.details p.card-title - !{card.productDisplayName} + span + | !{card.productDisplayName} + if card.productMetrics && card.productMetrics.length + - metric = card.productMetrics[0].ratingsAndReviews + - review = metric.productReviews + span.total-stars + span.actual-stars(style='width:#{metric.productRating*20}%;') + if review + a.reviews(href="#{review.link}", target="_blank") !{review.count} reviews a.json-toggle.pull-right(href="#", title="See raw JSON") {...} div.summary +cardNameImage(card)(data-toggle='tooltip', title='#{(card.productKeywords || []).join(", ")}') +marketingCopy(card) - div.apply.json-toggle.pull-right(title="Apply Now Link Disabled in Sandbox") + div.apply.json-toggle.pull-right +applyButton(card) p with CapitalOne sup ® @@ -74,7 +87,7 @@ block modals div#customer-info.modal.fade(tabindex='-1', role='dialog') div.modal-dialog div.modal-content - form(action='/offers', method='POST') + form(action='/offers', method='POST', name="prequalification") input(type="hidden" name="_csrf" value="#{csrfToken}") div.modal-header button.close(type='button', data-dismiss='modal', aria-label='close') @@ -83,9 +96,13 @@ block modals div.modal-body include ./includes/customer-form div.modal-footer + span.text-danger.hidden + | Errors detected. + |    button.btn.btn-default(type='button', data-dismiss='modal') | Close button.btn.btn-primary(type='submit') | See Offers include ./includes/raw-json-modal - + include ./includes/login-modal + include ./includes/prefill-accept-modal diff --git a/views/layout.jade b/views/layout.jade index 90beec0..1e54599 100644 --- a/views/layout.jade +++ b/views/layout.jade @@ -24,7 +24,7 @@ mixin cardNameImage(card) img.card-name(src='/images/default-card.png')&attributes(attributes) mixin applyButton(card) - a.btn.btn.btn-lg.btn-success + a.btn.btn.btn-lg.btn-success.apply-btn(href="#{card.applyNowLink}", target="_blank") i.fa.fa-lock(aria-hidden="true") | APPLY NOW @@ -59,6 +59,7 @@ head link(rel='stylesheet', href='/css/style.css') script(src='https://code.jquery.com/jquery-2.2.0.min.js') script(src='/js/bootstrap.min.js') + script(src='/js/creditoffers.js') body nav.navbar.navbar-default.navbar-fixed-top div.container From 1d8e826dc2ffee1fe8dbc1e191ec9b5dcb73d61f Mon Sep 17 00:00:00 2001 From: Hogan Date: Mon, 6 Nov 2017 10:35:28 -0800 Subject: [PATCH 131/147] config --- .gitignore | 2 +- config.js | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 config.js diff --git a/.gitignore b/.gitignore index cbe7837..42d1d1c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ # Ignore config files. Users should alter the sample. -config.js +#config.js # Thumbs Thumbs.db diff --git a/config.js b/config.js new file mode 100644 index 0000000..1ad943e --- /dev/null +++ b/config.js @@ -0,0 +1,59 @@ +/* +Copyright 2017 Capital One Services, LLC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and limitations under the License. +*/ + +/* + * Copy this file to config.js and enter your configuration info. + * config.js should not be under version control since it contains your + * client_id and client_secret. + */ +var oauthHost, creditOffersHost, clientID, clientSecret; +switch (process.env.C1_ENV) { + case "production": + oauthHost = 'https://api.capitalone.com'; + creditOffersHost = 'https://api.capitalone.com'; + //clientID = ''; + //clientSecret = ''; + break; + case "development": + oauthHost = 'https://apiit.capitalone.com'; + creditOffersHost = 'https://apiit.capitalone.com'; + clientID = 'a7ba9051c605440185d010b90a4cbd0c'; + clientSecret = '2cb24843ef0f60e7253da6440d3e5b94'; + break; + case "sandbox": + default: + oauthHost = 'https://api-sandbox.capitalone.com'; + creditOffersHost = 'https://api-sandbox.capitalone.com'; + clientID = '153f83dc92444613a3a46f7a0fd9622f'; + clientSecret = '2619bb1bc9abadfee5ad70d7268fa876'; + break; +} + +module.exports = { + // Settings for connecting to the Credit Offers API + creditOffers: { + client: { + // The URL of the Credit Offers environment you are connecting to. + url: creditOffersHost, + apiVersion: 3 + }, + oauth: { + tokenURL: oauthHost + '/oauth2/token', + // The clientId and clientSecret you received when registering your app. + clientID: clientID, + clientSecret: clientSecret + } + } +} From df7426638bffa0a6526b6856d6a9eed39ba03b2d Mon Sep 17 00:00:00 2001 From: Hogan Date: Mon, 6 Nov 2017 10:50:11 -0800 Subject: [PATCH 132/147] QA config --- config.js | 47 ++++++++++++++++++++++++++--------------------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/config.js b/config.js index 1ad943e..4e82775 100644 --- a/config.js +++ b/config.js @@ -19,27 +19,32 @@ See the License for the specific language governing permissions and limitations * client_id and client_secret. */ var oauthHost, creditOffersHost, clientID, clientSecret; -switch (process.env.C1_ENV) { - case "production": - oauthHost = 'https://api.capitalone.com'; - creditOffersHost = 'https://api.capitalone.com'; - //clientID = ''; - //clientSecret = ''; - break; - case "development": - oauthHost = 'https://apiit.capitalone.com'; - creditOffersHost = 'https://apiit.capitalone.com'; - clientID = 'a7ba9051c605440185d010b90a4cbd0c'; - clientSecret = '2cb24843ef0f60e7253da6440d3e5b94'; - break; - case "sandbox": - default: - oauthHost = 'https://api-sandbox.capitalone.com'; - creditOffersHost = 'https://api-sandbox.capitalone.com'; - clientID = '153f83dc92444613a3a46f7a0fd9622f'; - clientSecret = '2619bb1bc9abadfee5ad70d7268fa876'; - break; -} +// switch (process.env.C1_ENV) { +// case "production": +// oauthHost = 'https://api.capitalone.com'; +// creditOffersHost = 'https://api.capitalone.com'; +// //clientID = ''; +// //clientSecret = ''; +// break; +// case "development": +// oauthHost = 'https://apiit.capitalone.com'; +// creditOffersHost = 'https://apiit.capitalone.com'; +// clientID = 'a7ba9051c605440185d010b90a4cbd0c'; +// clientSecret = '2cb24843ef0f60e7253da6440d3e5b94'; +// break; +// case "sandbox": +// default: +// oauthHost = 'https://api-sandbox.capitalone.com'; +// creditOffersHost = 'https://api-sandbox.capitalone.com'; +// clientID = '153f83dc92444613a3a46f7a0fd9622f'; +// clientSecret = '2619bb1bc9abadfee5ad70d7268fa876'; +// break; +// } + +oauthHost = 'https://apiit.capitalone.com'; +creditOffersHost = 'https://apiit.capitalone.com'; +clientID = 'a7ba9051c605440185d010b90a4cbd0c'; +clientSecret = '2cb24843ef0f60e7253da6440d3e5b94'; module.exports = { // Settings for connecting to the Credit Offers API From f3d41bb1dece891e21d60e8979e7aa8bc6c35fe2 Mon Sep 17 00:00:00 2001 From: Hogan Date: Tue, 7 Nov 2017 16:18:22 -0800 Subject: [PATCH 133/147] finalized fixes for prefill and r&r --- README.md | 6 +++++- public/js/creditoffers.js | 4 +++- views/includes/prefill-accept-modal.jade | 18 ++++++++++-------- views/index.jade | 3 ++- views/layout.jade | 3 ++- 5 files changed, 22 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 8dd9f5b..9eda4e8 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ This reference app illustrates the use of the Credit Offers API to * Collect customer information and retrieve a list of targeted product offers for display using the `/credit-offers/prequalifications` endpoint * Send acknowledgement to Capital One that the targeted product offers have been displayed using the `/credit-offers/prequalifications/{prequalificationId}` endpoint * Retrieve and display prequalification summary info using the `/credit-offers/prequalifications-summary` endpoint +* Exemplify signing in and submitting example user data to the `/credit-offers/prefill-acceptance` endpoint Some additional API features that are **not** directly illustrated by this app include: @@ -45,11 +46,14 @@ From the project root: Navigate to http://localhost:3000. This will retrieve a list of Consumer card products from the API and display simple information about each. From here, you can try a few simple things: - * In sandbox mode, “Apply now” button will not work * Toggle the card type to 'Business' to request and display a list of business card products from the API * Click on the 'Find Pre-Qualified Offers' button to launch a simple customer information form and test out the pre-qualification API behavior. The results screen will also perform two asynchronous calls: * POST to `/credit-offers/prequalifications/{prequalificationId}` to acknowledge that the results were displayed to the customer * GET from the `/credit-offers/prequalifications-summary` endpoint to display simple pre-qualification statistics at the bottom of the page + * Click on the 'Sign in' button and use prepared credentials to simulate a session. Now, click on any products 'Apply Now' button. A confirmation window will launch, asking you to confirm whether you would like to prefill application information. Confirming will initiate a POST request to the `/credit-offers/prefill-acceptance` endpoint and append the returned applicantDetailsKey to the application link. + * username / password + * jsmith / Reference@123 + * rhubbard / Reference@123 #### A note about errors diff --git a/public/js/creditoffers.js b/public/js/creditoffers.js index a3e666d..533eb27 100644 --- a/public/js/creditoffers.js +++ b/public/js/creditoffers.js @@ -1,7 +1,9 @@ $(function() { $('.apply-btn').click(function(evt) { if ($('.auth-btn').hasClass('logout-btn')) { - $('form[name="prefill-acceptance"]').data('link', $(this).attr('href')) + let href = $(this).attr('href') + $('form[name="prefill-acceptance"]').data('link', href) + $('a.deny-prefill').attr('href', href) $('#prefill-acceptance').modal().on('hidden.bs.modal', () => { $('#prefill-acceptance .text-danger').addClass('hidden') }) diff --git a/views/includes/prefill-accept-modal.jade b/views/includes/prefill-accept-modal.jade index 8dec316..46b95e1 100644 --- a/views/includes/prefill-accept-modal.jade +++ b/views/includes/prefill-accept-modal.jade @@ -33,11 +33,13 @@ div#prefill-acceptance.modal.fade(tabindex='-1', role='dialog') li.list-group-item Income li.list-group-item Type of Bank Account you may have (if any) div.modal-footer - span.text-danger.hidden - | Prefill attempt failed - i.spinner.fa.fa-spinner.fa-pulse.fa-fw.hidden - |    - button.btn.btn-default(type='button', data-dismiss='modal') - | Close - button.btn.btn-primary(type='submit') - | Accept + div + span.text-danger.hidden + | Prefill attempt failed + i.spinner.fa.fa-spinner.fa-pulse.fa-fw.hidden + |    + button.btn.btn-default(type='button', data-dismiss='modal') + | Close + button.btn.btn-primary(type='submit') + | Accept + a.deny-prefill(target="_blank") Don't prefill information diff --git a/views/index.jade b/views/index.jade index 8526d7c..202833b 100644 --- a/views/index.jade +++ b/views/index.jade @@ -57,7 +57,8 @@ block content span.total-stars span.actual-stars(style='width:#{metric.productRating*20}%;') if review - a.reviews(href="#{review.link}", target="_blank") !{review.count} reviews + - link = review.link.replace('/mpid/', '/12345/') + a.reviews(href="#{link}", target="_blank") !{review.count} reviews a.json-toggle.pull-right(href="#", title="See raw JSON") {...} div.summary +cardNameImage(card)(data-toggle='tooltip', title='#{(card.productKeywords || []).join(", ")}') diff --git a/views/layout.jade b/views/layout.jade index 1e54599..5013d84 100644 --- a/views/layout.jade +++ b/views/layout.jade @@ -24,7 +24,8 @@ mixin cardNameImage(card) img.card-name(src='/images/default-card.png')&attributes(attributes) mixin applyButton(card) - a.btn.btn.btn-lg.btn-success.apply-btn(href="#{card.applyNowLink}", target="_blank") + - link = card.applyNowLink.replace('/mpid/', '/12345/') + a.btn.btn.btn-lg.btn-success.apply-btn(href="#{link}", target="_blank") i.fa.fa-lock(aria-hidden="true") | APPLY NOW From 7a593ae94c0ad50cbb7bf8c40b8765abfd6bc243 Mon Sep 17 00:00:00 2001 From: Hogan Date: Tue, 7 Nov 2017 16:23:45 -0800 Subject: [PATCH 134/147] ignore config.js again --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 42d1d1c..cbe7837 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ # Ignore config files. Users should alter the sample. -#config.js +config.js # Thumbs Thumbs.db From 3d5f50d4b0fbce52b0bf3f53bc3fc0c58ff94b13 Mon Sep 17 00:00:00 2001 From: Hogan Date: Tue, 7 Nov 2017 16:24:46 -0800 Subject: [PATCH 135/147] remove config.js again --- config.js | 64 ------------------------------------------------------- 1 file changed, 64 deletions(-) delete mode 100644 config.js diff --git a/config.js b/config.js deleted file mode 100644 index 4e82775..0000000 --- a/config.js +++ /dev/null @@ -1,64 +0,0 @@ -/* -Copyright 2017 Capital One Services, LLC - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and limitations under the License. -*/ - -/* - * Copy this file to config.js and enter your configuration info. - * config.js should not be under version control since it contains your - * client_id and client_secret. - */ -var oauthHost, creditOffersHost, clientID, clientSecret; -// switch (process.env.C1_ENV) { -// case "production": -// oauthHost = 'https://api.capitalone.com'; -// creditOffersHost = 'https://api.capitalone.com'; -// //clientID = ''; -// //clientSecret = ''; -// break; -// case "development": -// oauthHost = 'https://apiit.capitalone.com'; -// creditOffersHost = 'https://apiit.capitalone.com'; -// clientID = 'a7ba9051c605440185d010b90a4cbd0c'; -// clientSecret = '2cb24843ef0f60e7253da6440d3e5b94'; -// break; -// case "sandbox": -// default: -// oauthHost = 'https://api-sandbox.capitalone.com'; -// creditOffersHost = 'https://api-sandbox.capitalone.com'; -// clientID = '153f83dc92444613a3a46f7a0fd9622f'; -// clientSecret = '2619bb1bc9abadfee5ad70d7268fa876'; -// break; -// } - -oauthHost = 'https://apiit.capitalone.com'; -creditOffersHost = 'https://apiit.capitalone.com'; -clientID = 'a7ba9051c605440185d010b90a4cbd0c'; -clientSecret = '2cb24843ef0f60e7253da6440d3e5b94'; - -module.exports = { - // Settings for connecting to the Credit Offers API - creditOffers: { - client: { - // The URL of the Credit Offers environment you are connecting to. - url: creditOffersHost, - apiVersion: 3 - }, - oauth: { - tokenURL: oauthHost + '/oauth2/token', - // The clientId and clientSecret you received when registering your app. - clientID: clientID, - clientSecret: clientSecret - } - } -} From d33d76a8605bc874ccc4625e0b6733390b3240c4 Mon Sep 17 00:00:00 2001 From: Hogan Date: Tue, 7 Nov 2017 16:32:07 -0800 Subject: [PATCH 136/147] update dependency licenses --- credit-offers_notices.md | 1 + generateUsers.js | 10 ---------- 2 files changed, 1 insertion(+), 10 deletions(-) delete mode 100644 generateUsers.js diff --git a/credit-offers_notices.md b/credit-offers_notices.md index f8bc057..64a35e7 100644 --- a/credit-offers_notices.md +++ b/credit-offers_notices.md @@ -46,6 +46,7 @@ * [content-security-policy-builder@1.0.0](https://github.com/helmetjs/content-security-policy-builder) - MIT * [content-type@1.0.2](https://github.com/jshttp/content-type) - MIT * [cookie-parser@1.3.5](https://github.com/expressjs/cookie-parser) - MIT + * [cookie-session@1.3.2](https://github.com/expressjs/cookie-session) - MIT * [cookie-signature@1.0.6](https://github.com/visionmedia/node-cookie-signature) - MIT * [cookie@0.1.3](https://github.com/jshttp/cookie) - MIT * [cookie@0.1.5](https://github.com/jshttp/cookie) - MIT diff --git a/generateUsers.js b/generateUsers.js deleted file mode 100644 index 338fd59..0000000 --- a/generateUsers.js +++ /dev/null @@ -1,10 +0,0 @@ -var bcrypt = require('bcrypt') -const saltRounds = 10; - -bcrypt.hash('Reference@123', saltRounds, function(err, salt) { - console.log('salt', salt) -}) - -bcrypt.hash('Reference@123', saltRounds, function(err, salt) { - console.log('salt', salt) -}) From dde49afddd786424d4ef4700d569e9e84907977f Mon Sep 17 00:00:00 2001 From: Hogan Date: Tue, 5 Dec 2017 13:18:33 -0800 Subject: [PATCH 137/147] update ejs package --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 40a4add..acabbf6 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "cookie-session": "^2.0.0-beta.3", "csurf": "1.8.3", "debug": "2.2.0", - "ejs": "2.3.4", + "ejs": "~2.5.5", "express": "4.13.4", "express-validator": "2.21.0", "font-awesome": "4.7.0", From 2a1d0632bfc1481231e271517d4e27c8923756d3 Mon Sep 17 00:00:00 2001 From: Hogan Date: Tue, 5 Dec 2017 13:28:33 -0800 Subject: [PATCH 138/147] Remove PQ Summary --- creditoffers/prequalification.js | 31 ------------------------------- routes/offers.js | 15 --------------- views/offers.jade | 30 ------------------------------ 3 files changed, 76 deletions(-) diff --git a/creditoffers/prequalification.js b/creditoffers/prequalification.js index 0d586ac..b1cdaee 100644 --- a/creditoffers/prequalification.js +++ b/creditoffers/prequalification.js @@ -65,34 +65,3 @@ Prequalification.prototype.acknowledge = function acknowledge (prequalificationI body: { 'hasBeenAcknowledged': true } }, callback) } - -/** - * Retrieves a set of summary info about your client's usage of the prequalification API - */ -Prequalification.prototype.getSummary = function getSummary (options, callback) { - var defaults = { - fromDate: null, - toDate: null, - minIncome: null, - maxIncome: null - } - - options = _.defaults({}, options, defaults) - var dateFormat = 'YYYY-MM-DD', - fromDate = options.fromDate && moment(options.fromDate).format(dateFormat), - toDate = options.toDate && moment(options.toDate).format(dateFormat), - // Build the query string values, dropping any nulls - query = _.omitBy({ - fromDate: fromDate, - toDate: toDate, - minIncome: options.minIncome, - maxIncome: options.maxIncome - }, _.isNull) - - this.client.sendRequest({ - url: '/credit-offers/prequalifications-summary', - useOAuth: true, - method: 'GET', - qs: query - }, callback) -} diff --git a/routes/offers.js b/routes/offers.js index 99bba21..660d994 100644 --- a/routes/offers.js +++ b/routes/offers.js @@ -142,20 +142,5 @@ module.exports = function(client) { }) }) - // GET prequalification summary data for this client - router.get('/summary', function(req, res, next) { - debug('Getting prequal summary data') - - // Get unfiltered summary - client.prequalification.getSummary({}, function(err, response) { - if (err) { - debug('Error in API call', err) - res.status(500).send() - return - } - res.json(response) - }) - }) - return router } diff --git a/views/offers.jade b/views/offers.jade index 2eb4876..a8fad6f 100644 --- a/views/offers.jade +++ b/views/offers.jade @@ -60,12 +60,6 @@ block content div.info div.info-label Credit Needed div.info-value= (card.creditRating || []).join(', ') || 'N/A' - footer#prequal-stats-summary.navbar-fixed-bottom - i.fa.fa-pie-chart(aria-hidden="true") - div.stat-text Loading Stats... - div.acknowledgement - span.status-text Acknowledging... - i.status-icon.fa.fa-circle-o block modals include ./includes/raw-json-modal @@ -99,27 +93,3 @@ block scripts $('.acknowledgement .status-icon').removeClass('fa-circle-o').addClass('fa-circle') }) }) - - $(function () { - function makeStat(title, value) { - var stat = $('
').addClass('stat') - stat.append($('').addClass('stat-title').html(title + ': ')) - stat.append($('').html(value)) - return stat - } - - $.getJSON('/offers/summary') - .done(function (data) { - var parent = $('#prequal-stats-summary .stat-text').html(''), - stats = [ - makeStat('Consumer Cards', data.consumerCardCount), - makeStat('Non-PreQualified', data.nonPrequalifiedCount), - makeStat('PreQualified', data.prequalifiedCount), - makeStat('Total', data.total) - ] - parent.append(stats) - }) - .fail(function () { - $('#prequal-stats-summary .stat-text').html('FAILED to get prequalification summary data!') - }) - }) From ef0f4a652319ef4242cee4a099ab82424ab52dc4 Mon Sep 17 00:00:00 2001 From: Hogan Date: Tue, 5 Dec 2017 13:29:07 -0800 Subject: [PATCH 139/147] cleanup js file --- public/js/creditoffers.js | 30 +----------------------------- 1 file changed, 1 insertion(+), 29 deletions(-) diff --git a/public/js/creditoffers.js b/public/js/creditoffers.js index cb2d650..702d515 100644 --- a/public/js/creditoffers.js +++ b/public/js/creditoffers.js @@ -9,7 +9,7 @@ $(function() { } }) - //$('#login').modal('show') + //Prefill append applicantDetailsKey to link url $('form[name="prefill-acceptance"]').submit(function(evt) { let link = $(this).data('link') let $spinner = $(this).find('.spinner') @@ -39,32 +39,4 @@ $(function() { }); evt.preventDefault(); }); - - // $('form[name="prequalification"]').submit(function(evt) { - // evt.preventDefault(); - // $form = $(this) - // $.ajax({ - // type: "POST", - // url: this.action, - // data: $(this).serialize(), // serializes the form's elements. - // xhrFields: { - // withCredentials: true - // }, - // success: function(data) { - // $form[0].submit() - // }, - // error: function(err) { - // if (err.responseJSON) { - // $.each($form.serializeArray(), function(i, field) { - // $formInputParent = $(`[name="${field.name}"]`).parents('.form-group') - // if (err.responseJSON[field.name]) $formInputParent.addClass('has-error') - // else $formInputParent.removeClass('has-error') - // }) - // $form.find('.modal-footer > .text-danger.hidden').removeClass('hidden') - // } else { - // $form[0].submit() - // } - // } - // }); - // }); }); From e8cdca2fa9ac328efa1a88d6cc0446675b52ce37 Mon Sep 17 00:00:00 2001 From: Hogan Date: Tue, 16 Jan 2018 14:41:55 -0800 Subject: [PATCH 140/147] remove unnecessary oauth host --- config.js.sample | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/config.js.sample b/config.js.sample index 5ef4f06..65df113 100644 --- a/config.js.sample +++ b/config.js.sample @@ -18,7 +18,6 @@ See the License for the specific language governing permissions and limitations * config.js should not be under version control since it contains your * client_id and client_secret. */ -var oauthHost = 'https://api-sandbox.capitalone.com' var creditOffersHost = 'https://api-sandbox.capitalone.com' module.exports = { @@ -30,7 +29,7 @@ module.exports = { apiVersion: 3 }, oauth: { - tokenURL: oauthHost + '/oauth2/token', + tokenURL: creditOffersHost + '/oauth2/token', // The clientId and clientSecret you received when registering your app. clientID: 'COF_CLIENT_ID', clientSecret: 'COF_CLIENT_SECRET' From c428d28352c7643509577ad3bc0ad3ed2c2dc9fb Mon Sep 17 00:00:00 2001 From: Hogan Date: Tue, 6 Feb 2018 14:57:52 -0800 Subject: [PATCH 141/147] Default PQ user info if signed in --- public/js/creditoffers.js | 34 ++++---- routes/index.js | 29 ++++--- routes/offers.js | 5 +- views/includes/customer-form.jade | 128 ++++++++---------------------- views/index.jade | 6 +- views/layout.jade | 2 +- views/offers.jade | 3 +- 7 files changed, 76 insertions(+), 131 deletions(-) diff --git a/public/js/creditoffers.js b/public/js/creditoffers.js index a016fd6..13f894b 100644 --- a/public/js/creditoffers.js +++ b/public/js/creditoffers.js @@ -1,14 +1,12 @@ $(function() { - $('.apply-btn').click(function(evt) { - if ($('.auth-btn').hasClass('logout-btn')) { - let href = $(this).attr('href') - $('form[name="prefill-acceptance"]').data('link', href) - $('a.deny-prefill').attr('href', href) - $('#prefill-acceptance').modal().on('hidden.bs.modal', () => { - $('#prefill-acceptance .text-danger').addClass('hidden') - }) - evt.preventDefault() - } + $('.signed-in .apply-btn').click(function(evt) { + let href = $(this).attr('href') + $('form[name="prefill-acceptance"]').data('link', href) + $('a.deny-prefill').attr('href', href) + $('#prefill-acceptance').modal().on('hidden.bs.modal', () => { + $('#prefill-acceptance .text-danger').addClass('hidden') + }) + evt.preventDefault() }) //Prefill append applicantDetailsKey to link url @@ -24,19 +22,23 @@ $(function() { withCredentials: true }, success: function(data) { - link += encodeURIComponent(`&applicantDetailsKey=${data.applicantDetailsKey}`) + link += encodeURIComponent(`?applicantDetailsKey=a-${data.applicantDetailsKey}`) }, error: function(err) { $('#prefill-acceptance .text-danger.hidden').removeClass('hidden') }, complete: function() { - $spinner.addClass('hidden') - let win = window.open(link, '_blank') - if (win) { - win.focus(); + if (link.indexOf('/mpid/') > -1) { + console.log('Out of production, Apply Now links are non-functional.', link) } else { - alert('Please allow popups for this website'); + let win = window.open(link, '_blank') + if (win) { + win.focus(); + } else { + alert('Please allow popups for this website'); + } } + $spinner.addClass('hidden') } }); evt.preventDefault(); diff --git a/routes/index.js b/routes/index.js index 1e4a0be..52381d1 100644 --- a/routes/index.js +++ b/routes/index.js @@ -62,7 +62,6 @@ module.exports = function(client) { if (err) { return next(err) } - cards = _.map(_.get(data, 'products', []), productViewModel) res.render('index', { csrfToken: req.csrfToken(), @@ -70,7 +69,13 @@ module.exports = function(client) { currentCardType: requestedCardType.name, cardTypes: cardTypes, cards: cards, - user: req.session.user + user: req.session.user || { address: {}}, + stateCodes: require('../validation/stateCodes'), + bankAccountSummaryOptions: [ + { value: 'CheckingAndSavings', key: 'Checking & Savings' }, + { value: 'CheckingOnly', key: 'Checking Only' }, + { value: 'SavingsOnly', key: 'Savings Only' }, + { value: 'Neither', key: 'Neither', default: true }] }) } @@ -86,11 +91,12 @@ module.exports = function(client) { }) router.post('/login', csrfProtection, function(req, res, next) { - let user = require('../creditoffers/users').find((user) => { - return user.username == req.body.username - }) || {} + let user = require('../creditoffers/users').find((user) => { return user.username == req.body.username }) || {} require('bcrypt').compare(req.body.password, user.password, (err, matched) => { if (matched) { + user = user.details || {} + user.address = user.addresses[0] || {} + user.emailAddress = (user.emailAddresses[0] || {}).emailAddress req.session.user = user } res.redirect('/') @@ -103,16 +109,9 @@ module.exports = function(client) { }) router.post('/prefill-acceptance', csrfProtection, function(req, res, next) { - let user = req.session.user || {} - client.products.postPrefillAcceptance(user.details, (err, response) => { - if (err || !response.applicantDetailsKey) { - res.status(400).send() - // res.json({ - // applicantDetailsKey: "testKey" - // }) - } else { - res.json(response) - } + client.products.postPrefillAcceptance(req.session.user, (err, response) => { + if (err || !response.applicantDetailsKey) res.status(400).send() + else res.json(response) }) }) diff --git a/routes/offers.js b/routes/offers.js index 660d994..37554b1 100644 --- a/routes/offers.js +++ b/routes/offers.js @@ -82,12 +82,13 @@ module.exports = function(client) { .value() var viewModel = { + csrfToken: req.csrfToken(), title: 'Credit Offers', isPrequalified: response.isPrequalified, prequalificationId: response.prequalificationId, - products: productViewModels + products: productViewModels, + user: req.session.user || { address: {}} } - res.render('offers', viewModel) }) }) diff --git a/views/includes/customer-form.jade b/views/includes/customer-form.jade index af0449f..867acf7 100644 --- a/views/includes/customer-form.jade +++ b/views/includes/customer-form.jade @@ -15,116 +15,62 @@ //- SPDX-License-Identifier: Apache-2.0 //- A re-usable bootstrap text input -- sandboxEnv = this.process.env.C1_ENV == 'sandbox' +- stateCodes = typeof stateCodes != 'undefined' ? stateCodes : [] +- bankAccountSummaryOptions = typeof bankAccountSummaryOptions != 'undefined' ? bankAccountSummaryOptions : [] +- user = typeof user != 'undefined' ? user : { address : {}} +- var _default + mixin textinput(name, label, required) div.form-group(class={required: required}) label(for=name)= label - input.form-control(type='text', name=name, placeholder=attributes.placeholder, required=required || false, value=sandboxEnv && attributes.default ? attributes.default : '') + input.form-control(type='text', name=name, placeholder=attributes.placeholder, required=required || false, value=attributes.default) + mixin select(name, label, required) div.form-group(class={required: required}) label(for=name)= label - select.form-control(name=name, selected=attributes.selected || '') + select.form-control(name=name) block -mixin taxIdOptions() - option(value='555555555') 555555555 (No Prequalification Offer) - option(value='666666666') 666666666 (Single Prequalification Offer) - option(value='777777777') 777777777 (Multiple Prequalified Offers) - option(value='888888888') 888888888 (Max Prequalification Offers) - mixin stateOptions() - option(value='AL') AL - option(value='AK') AK - option(value='AS') AS - option(value='AZ') AZ - option(value='AR') AR - option(value='CA') CA - option(value='CO') CO - option(value='CT') CT - option(value='DE') DE - option(value='DC') DC - option(value='FM') FM - option(value='FL') FL - option(value='GA') GA - option(value='GU') GU - option(value='HI') HI - option(value='ID') ID - option(value='IL') IL - option(value='IN') IN - option(value='IA') IA - option(value='KS') KS - option(value='KY') KY - option(value='LA') LA - option(value='ME') ME - option(value='MH') MH - option(value='MD') MD - if sandboxEnv - option(value='MA', selected='selected') MA - else - option(value='MA') MA - option(value='MI') MI - option(value='MN') MN - option(value='MS') MS - option(value='MO') MO - option(value='MT') MT - option(value='NE') NE - option(value='NV') NV - option(value='NH') NH - option(value='NJ') NJ - option(value='NM') NM - option(value='NY') NY - option(value='NC') NC - option(value='ND') ND - option(value='MP') MP - option(value='OH') OH - option(value='OK') OK - option(value='OR') OR - option(value='PW') PW - option(value='PA') PA - option(value='PR') PR - option(value='RI') RI - option(value='SC') SC - option(value='SD') SD - option(value='TN') TN - option(value='TX') TX - option(value='UT') UT - option(value='VT') VT - option(value='VI') VI - option(value='VA') VA - option(value='WA') WA - option(value='WV') WV - option(value='WI') WI - option(value='WY') WY - option(value='DC') DC - option(value='AA') AA - option(value='AE') AE - option(value='AP') AP + each stateCode in stateCodes + if stateCode == user.address.stateCode + option(value=stateCode, selected='selected')= stateCode + else + option(value=stateCode)= stateCode + +mixin bankAccountSummary() + if bankAccountSummaryOptions.length + +select('bankAccountSummary', 'Type of Bank Accounts') + each option in bankAccountSummaryOptions + - let value = option.value + - let key = option.key + if key == user.bankAccountSummary || (option.default && typeof _default != 'undefined') + - _default = option + option(value=value, selected='selected')= key + else + option(value=value)= key -+textinput('firstName', 'First Name', true)(default='Rose') ++textinput('firstName', 'First Name', true)(default=user.firstName) +textinput('middleName', 'Middle Name') -+textinput('lastName', 'Last Name', true)(default='Dean') ++textinput('lastName', 'Last Name', true)(default=user.lastName) +textinput('nameSuffix', 'Suffix')(placeholder='E.g. Jr, Sr') -+textinput('addressLine1', 'Address Line 1', true)(default='88 Suffolk St') ++textinput('addressLine1', 'Address Line 1', true)(default=user.address.addressLine1) +textinput('addressLine2', 'Address Line 2') +textinput('addressLine3', 'Address Line 3') +textinput('addressLine4', 'Address Line 4') -+textinput('city', 'City', true)(default='Springfield') ++textinput('city', 'City', true)(default=user.address.city) +select('stateCode', 'State', true) +stateOptions() -+textinput('postalCode', 'Zip Code', true)(default='01109') ++textinput('postalCode', 'Zip Code', true)(default=user.address.postalCode) +select('addressType', 'Address Type') option(value='') option(value='Home') Home option(value='Business') Business -if sandboxEnv - +select('taxId', 'SSN', true) - +taxIdOptions() -else - +textinput('taxId', 'Last Four Digits of SSN', true) -+textinput('dateOfBirth', 'Birth Date', false)(placeholder='YYYY-MM-DD', default='1964-11-12') ++textinput('taxId', 'Last Four Digits of SSN', true) ++textinput('dateOfBirth', 'Birth Date', false)(placeholder='YYYY-MM-DD') div.form-note The following information is optional. However, it can help us show you offers that might match your needs and circumstances better. -+textinput('emailAddress', 'Email Address') ++textinput('emailAddress', 'Email Address')(default=user.emailAddress) +select('primaryBenefit', 'Most Desired Credit Card Benefit') option(value='NotSure') Not sure option(value='LowInterest') Low Interest Rate @@ -134,9 +80,5 @@ div.form-note The following information is optional. However, it can help us sho option(value='Excellent') Excellent option(value='Average') Average option(value='Rebuilding') Rebuilding -+textinput('annualIncome', 'Annual Income (dollars)') -+select('bankAccountSummary', 'Type of Bank Accounts') - option(value='CheckingAndSavings') Checking & Savings - option(value='CheckingOnly') Checking Only - option(value='SavingsOnly') Savings Only - option(value='Neither', selected) Neither ++textinput('annualIncome', 'Annual Income (dollars)')(default=user.annualIncome) ++bankAccountSummary() diff --git a/views/index.jade b/views/index.jade index 8526d7c..f0d392c 100644 --- a/views/index.jade +++ b/views/index.jade @@ -21,9 +21,9 @@ block navbar-custom button.btn.btn-md.navbar-btn.btn-success(role='button', data-toggle='modal', data-target='#customer-info') i.btn-img.fa.fa-credit-card(aria-hidden="true") | Find Pre-Qualified Offers - a.btn.btn-md.navbar-btn.btn-success.auth-btn&attributes(user ? { 'class': 'logout-btn', 'href': '/logout' } : { 'data-toggle': 'modal', 'data-target': '#login', 'role': 'button' }) - i.btn-img.fa(aria-hidden="true", class="#{ user ? fa-sign-out : fa-sign-in }") - = user ? 'Sign Out' : 'Sign In' + a.btn.btn-md.navbar-btn.btn-success.auth-btn&attributes(user.firstName ? { 'class': 'logout-btn', 'href': '/logout' } : { 'data-toggle': 'modal', 'data-target': '#login', 'role': 'button' }) + i.btn-img.fa(aria-hidden="true", class=user.firstName && user.firstName.length ? 'fa-sign-out' : 'fa-sign-in') + = user.firstName ? 'Sign Out' : 'Sign In' block content diff --git a/views/layout.jade b/views/layout.jade index 1e54599..15d008c 100644 --- a/views/layout.jade +++ b/views/layout.jade @@ -60,7 +60,7 @@ head script(src='https://code.jquery.com/jquery-2.2.0.min.js') script(src='/js/bootstrap.min.js') script(src='/js/creditoffers.js') - body + body(class=user && user.firstName && user.firstName.length ? 'signed-in' : '') nav.navbar.navbar-default.navbar-fixed-top div.container div.navbar-header diff --git a/views/offers.jade b/views/offers.jade index a8fad6f..020fed7 100644 --- a/views/offers.jade +++ b/views/offers.jade @@ -63,7 +63,8 @@ block content block modals include ./includes/raw-json-modal - + include ./includes/prefill-accept-modal + block scripts script. $(function() { From d5ea07f8eeb5849e306d792d8661551eac1d9c4e Mon Sep 17 00:00:00 2001 From: Hogan Date: Wed, 7 Feb 2018 12:29:24 -0800 Subject: [PATCH 142/147] display key in sanbox --- public/js/creditoffers.js | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/public/js/creditoffers.js b/public/js/creditoffers.js index 13f894b..3bb0043 100644 --- a/public/js/creditoffers.js +++ b/public/js/creditoffers.js @@ -1,16 +1,30 @@ $(function() { + if(!$('.signed-in').length) { + $('.apply-btn').addClass('disabled').parent().attr('title', 'Apply Now Link Disabled') + } + $('.signed-in .apply-btn').click(function(evt) { let href = $(this).attr('href') $('form[name="prefill-acceptance"]').data('link', href) - $('a.deny-prefill').attr('href', href) + + if (href.indexOf('/mpid/') > -1) { + $('#prefill-acceptance button[type="submit"]').removeClass('hidden') + $('#prefill-acceptance .modal-body .applicant-key-text').remove() + $('#prefill-acceptance .deny-prefill').remove() + } else { + $('a.deny-prefill').attr('href', href) + } + $('#prefill-acceptance').modal().on('hidden.bs.modal', () => { $('#prefill-acceptance .text-danger').addClass('hidden') }) + evt.preventDefault() }) //Prefill append applicantDetailsKey to link url $('form[name="prefill-acceptance"]').submit(function(evt) { + let applicantDetailsKey let link = $(this).data('link') let $spinner = $(this).find('.spinner') $spinner.removeClass('hidden') @@ -22,14 +36,21 @@ $(function() { withCredentials: true }, success: function(data) { - link += encodeURIComponent(`?applicantDetailsKey=a-${data.applicantDetailsKey}`) + applicantDetailsKey = data.applicantDetailsKey + link += encodeURIComponent(`?applicantDetailsKey=a-${applicantDetailsKey}`) }, error: function(err) { $('#prefill-acceptance .text-danger.hidden').removeClass('hidden') }, complete: function() { if (link.indexOf('/mpid/') > -1) { - console.log('Out of production, Apply Now links are non-functional.', link) + $('#prefill-acceptance button[type="submit"]').addClass('hidden') + $('#prefill-acceptance .modal-body').append( + ` +
You have been generated an Applicant Details Key:
${applicantDetailsKey}
+

In production, this would be appended to the end of the Apply Now link.

+ ` + ) } else { let win = window.open(link, '_blank') if (win) { From afa1a920df6e241c4ce4db1c792e24cb9d4bc084 Mon Sep 17 00:00:00 2001 From: Hogan Date: Wed, 7 Feb 2018 13:45:42 -0800 Subject: [PATCH 143/147] show applicant details in modal --- public/css/style.css | 9 ++++ public/js/creditoffers.js | 2 +- views/includes/prefill-accept-modal.jade | 53 +++++++++++++++++++++--- 3 files changed, 57 insertions(+), 7 deletions(-) diff --git a/public/css/style.css b/public/css/style.css index 02a739f..f75030d 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -226,3 +226,12 @@ footer#prequal-stats-summary { background-position: 0; } .btn-primary[disabled], .btn-primary[disabled]:hover, .btn-primary[disabled]:focus { background-color: #1976D2; } + + +#prefill-acceptance [class*="col-"], #prefill-acceptance .container-fluid { + padding: 0; +} + +#prefill-acceptance .container-fluid > [class*="col-"]:last-child { + text-align: right; +} diff --git a/public/js/creditoffers.js b/public/js/creditoffers.js index 3bb0043..554c636 100644 --- a/public/js/creditoffers.js +++ b/public/js/creditoffers.js @@ -37,7 +37,7 @@ $(function() { }, success: function(data) { applicantDetailsKey = data.applicantDetailsKey - link += encodeURIComponent(`?applicantDetailsKey=a-${applicantDetailsKey}`) + link += encodeURIComponent(`?applicantDetailsKey=${applicantDetailsKey}`) }, error: function(err) { $('#prefill-acceptance .text-danger.hidden').removeClass('hidden') diff --git a/views/includes/prefill-accept-modal.jade b/views/includes/prefill-accept-modal.jade index 46b95e1..3c4af5b 100644 --- a/views/includes/prefill-accept-modal.jade +++ b/views/includes/prefill-accept-modal.jade @@ -26,12 +26,53 @@ div#prefill-acceptance.modal.fade(tabindex='-1', role='dialog') | Save time by allowing us to prefill some of your application information. .bold Information we will attempt to prefill includes: ul.list-group - li.list-group-item Name (First, Middle, Last & Suffix) - li.list-group-item Address - li.list-group-item Phone Numbers - li.list-group-item Email Addresses - li.list-group-item Income - li.list-group-item Type of Bank Account you may have (if any) + li.list-group-item + .container-fluid + .col-xs-6 + | Name (First, Middle, Last & Suffix): + .col-xs-6 + !{user.firstName} !{user.middleName} !{user.lastName} + li.list-group-item + .container-fluid + .col-xs-2 + | Address: + .col-xs-10 + !{user.address.addressLine1} !{user.address.addressLine2} + if user.address.addressLine3 || user.address.addressLine4 + br + !{user.address.addressLine3} !{user.address.addressLine4} + br + !{user.address.city}, !{user.address.stateCode} !{user.address.postalCode} + li.list-group-item + .container-fluid + .col-xs-4 Phone Numbers: + .col-xs-8 + if user.telephoneNumbers && user.telephoneNumbers.length + each num in user.telephoneNumbers + !{num.phoneNumberType}: !{num.telephoneNumber} + br + li.list-group-item + .container-fluid + .col-xs-4 Email Addresses: + .col-xs-8 + if user.emailAddresses && user.emailAddresses.length + each address in user.emailAddresses + !{address.emailAddress} + br + + li.list-group-item + .container-fluid + .col-xs-3 Income: + .col-xs-9 + !{user.annualIncome} + li.list-group-item + .container-fluid + .col-xs-8 Type of Bank Account you may have (if any): + .col-xs-4 + each bankAccountSummary in bankAccountSummaryOptions + if bankAccountSummary.value === user.bankAccountSummary + !{bankAccountSummary.key} + div.modal-footer div span.text-danger.hidden From ae63fff5016e3699bc136bc08e229685a0176d0e Mon Sep 17 00:00:00 2001 From: Hogan Date: Wed, 14 Feb 2018 10:21:09 -0800 Subject: [PATCH 144/147] line fixes for jade --- views/includes/prefill-accept-modal.jade | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/views/includes/prefill-accept-modal.jade b/views/includes/prefill-accept-modal.jade index 3c4af5b..d537e7f 100644 --- a/views/includes/prefill-accept-modal.jade +++ b/views/includes/prefill-accept-modal.jade @@ -31,25 +31,25 @@ div#prefill-acceptance.modal.fade(tabindex='-1', role='dialog') .col-xs-6 | Name (First, Middle, Last & Suffix): .col-xs-6 - !{user.firstName} !{user.middleName} !{user.lastName} + | !{user.firstName} !{user.middleName} !{user.lastName} li.list-group-item .container-fluid .col-xs-2 | Address: .col-xs-10 - !{user.address.addressLine1} !{user.address.addressLine2} + | !{user.address.addressLine1} !{user.address.addressLine2} if user.address.addressLine3 || user.address.addressLine4 br - !{user.address.addressLine3} !{user.address.addressLine4} + | !{user.address.addressLine3} !{user.address.addressLine4} br - !{user.address.city}, !{user.address.stateCode} !{user.address.postalCode} + | !{user.address.city}, !{user.address.stateCode} !{user.address.postalCode} li.list-group-item .container-fluid .col-xs-4 Phone Numbers: .col-xs-8 if user.telephoneNumbers && user.telephoneNumbers.length each num in user.telephoneNumbers - !{num.phoneNumberType}: !{num.telephoneNumber} + | (!{num.phoneNumberType}) !{num.telephoneNumber} br li.list-group-item .container-fluid @@ -57,21 +57,20 @@ div#prefill-acceptance.modal.fade(tabindex='-1', role='dialog') .col-xs-8 if user.emailAddresses && user.emailAddresses.length each address in user.emailAddresses - !{address.emailAddress} + | !{address.emailAddress} br li.list-group-item .container-fluid .col-xs-3 Income: - .col-xs-9 - !{user.annualIncome} + .col-xs-9 !{user.annualIncome} li.list-group-item .container-fluid .col-xs-8 Type of Bank Account you may have (if any): .col-xs-4 each bankAccountSummary in bankAccountSummaryOptions if bankAccountSummary.value === user.bankAccountSummary - !{bankAccountSummary.key} + | !{bankAccountSummary.key} div.modal-footer div From 7a71c56fa4bbdabc41a80f4ce86e0b57e87e1871 Mon Sep 17 00:00:00 2001 From: Hogan Date: Fri, 23 Feb 2018 08:08:28 -0800 Subject: [PATCH 145/147] dateOfBirth for applicantDetailsKey --- creditoffers/users.js | 2 ++ views/includes/customer-form.jade | 2 +- views/includes/prefill-accept-modal.jade | 6 ++++++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/creditoffers/users.js b/creditoffers/users.js index 3d446ac..db1a01f 100644 --- a/creditoffers/users.js +++ b/creditoffers/users.js @@ -6,6 +6,7 @@ module.exports = [{ "middleName": "G", "lastName": "Hubbard", "nameSuffix": "Jr.", + "dateOfBirth": "1946-11-13", "addresses": [{ "addressLine1": "1230 Duck Fuss Lane", "addressLine2": "Apt 15X", @@ -34,6 +35,7 @@ module.exports = [{ details: { "firstName": "Jane", "lastName": "Smith", + "dateOfBirth": "1980-05-15", "addresses": [{ "addressLine1": "3828 Piermont Dr", "addressLine2": "Unit A", diff --git a/views/includes/customer-form.jade b/views/includes/customer-form.jade index 867acf7..d09ecec 100644 --- a/views/includes/customer-form.jade +++ b/views/includes/customer-form.jade @@ -67,7 +67,7 @@ mixin bankAccountSummary() option(value='Home') Home option(value='Business') Business +textinput('taxId', 'Last Four Digits of SSN', true) -+textinput('dateOfBirth', 'Birth Date', false)(placeholder='YYYY-MM-DD') ++textinput('dateOfBirth', 'Birth Date', false)(placeholder='YYYY-MM-DD')(default=user.dateOfBirth) div.form-note The following information is optional. However, it can help us show you offers that might match your needs and circumstances better. +textinput('emailAddress', 'Email Address')(default=user.emailAddress) diff --git a/views/includes/prefill-accept-modal.jade b/views/includes/prefill-accept-modal.jade index d537e7f..51f5af6 100644 --- a/views/includes/prefill-accept-modal.jade +++ b/views/includes/prefill-accept-modal.jade @@ -32,6 +32,12 @@ div#prefill-acceptance.modal.fade(tabindex='-1', role='dialog') | Name (First, Middle, Last & Suffix): .col-xs-6 | !{user.firstName} !{user.middleName} !{user.lastName} + li.list-group-item + .container-fluid + .col-xs-6 + | Date of Birth: + .col-xs-6 + | !{user.dateOfBirth} li.list-group-item .container-fluid .col-xs-2 From 419719df0a733e01db7e8026c5d4902944a21801 Mon Sep 17 00:00:00 2001 From: Hogan Date: Mon, 12 Mar 2018 09:47:39 -0700 Subject: [PATCH 146/147] handle when no bankAccountSummaryOptions --- views/includes/prefill-accept-modal.jade | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/views/includes/prefill-accept-modal.jade b/views/includes/prefill-accept-modal.jade index 51f5af6..522ff4b 100644 --- a/views/includes/prefill-accept-modal.jade +++ b/views/includes/prefill-accept-modal.jade @@ -74,9 +74,10 @@ div#prefill-acceptance.modal.fade(tabindex='-1', role='dialog') .container-fluid .col-xs-8 Type of Bank Account you may have (if any): .col-xs-4 - each bankAccountSummary in bankAccountSummaryOptions - if bankAccountSummary.value === user.bankAccountSummary - | !{bankAccountSummary.key} + if bankAccountSummaryOptions + each bankAccountSummary in bankAccountSummaryOptions + if bankAccountSummary.value === user.bankAccountSummary + | !{bankAccountSummary.key} div.modal-footer div From 6dc898975ac920f948b77f4408c29ca02a749f07 Mon Sep 17 00:00:00 2001 From: Hogan Date: Mon, 12 Mar 2018 09:58:39 -0700 Subject: [PATCH 147/147] remove moment dependency --- creditoffers/prequalification.js | 1 - package.json | 1 - routes/offers.js | 7 ++++++- views/includes/customer-form.jade | 2 +- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/creditoffers/prequalification.js b/creditoffers/prequalification.js index b1cdaee..42f5911 100644 --- a/creditoffers/prequalification.js +++ b/creditoffers/prequalification.js @@ -18,7 +18,6 @@ SPDX-License-Identifier: Apache-2.0 var request = require('request') var debug = require('debug')('credit-offers:api-client') -var moment = require('moment') var _ = require('lodash') /** diff --git a/package.json b/package.json index acabbf6..45647ed 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,6 @@ "html-entities": "1.2.0", "jade": "1.11.0", "lodash": "4.1.0", - "moment": "2.16.0", "morgan": "1.6.1", "request": "2.69.0", "sanitize-html": "1.13.0", diff --git a/routes/offers.js b/routes/offers.js index 37554b1..0e0fe02 100644 --- a/routes/offers.js +++ b/routes/offers.js @@ -87,7 +87,12 @@ module.exports = function(client) { isPrequalified: response.isPrequalified, prequalificationId: response.prequalificationId, products: productViewModels, - user: req.session.user || { address: {}} + user: req.session.user || { address: {}}, + bankAccountSummaryOptions: [ + { value: 'CheckingAndSavings', key: 'Checking & Savings' }, + { value: 'CheckingOnly', key: 'Checking Only' }, + { value: 'SavingsOnly', key: 'Savings Only' }, + { value: 'Neither', key: 'Neither', default: true }] } res.render('offers', viewModel) }) diff --git a/views/includes/customer-form.jade b/views/includes/customer-form.jade index d09ecec..614ce43 100644 --- a/views/includes/customer-form.jade +++ b/views/includes/customer-form.jade @@ -67,7 +67,7 @@ mixin bankAccountSummary() option(value='Home') Home option(value='Business') Business +textinput('taxId', 'Last Four Digits of SSN', true) -+textinput('dateOfBirth', 'Birth Date', false)(placeholder='YYYY-MM-DD')(default=user.dateOfBirth) ++textinput('dateOfBirth', 'Birth Date', false)(placeholder='YYYY-MM-DD', default=user.dateOfBirth) div.form-note The following information is optional. However, it can help us show you offers that might match your needs and circumstances better. +textinput('emailAddress', 'Email Address')(default=user.emailAddress)