diff --git a/.eslintrc.json b/.eslintrc.json index ceacd88..2c6904e 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -3,4 +3,4 @@ "env": { "jest": true } -} \ No newline at end of file +} diff --git a/config/config.json b/config/config.json index 6c2f503..993f2a5 100644 --- a/config/config.json +++ b/config/config.json @@ -1,14 +1,14 @@ { "development": { "username": "teamsix", - "password": null, + "password": "root", "database": "wallet_development", "host": "127.0.0.1", "dialect": "postgres" }, "test": { "username": "teamsix", - "password": null, + "password": "root", "database": "wallet_test", "host": "127.0.0.1", "dialect": "postgres" diff --git a/migrations/20180209081342-create-auth.js b/migrations/20180209081342-create-auth.js new file mode 100644 index 0000000..2f5d65e --- /dev/null +++ b/migrations/20180209081342-create-auth.js @@ -0,0 +1,27 @@ +'use strict'; +module.exports = { + up: (queryInterface, Sequelize) => { + return queryInterface.createTable('auths', { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: Sequelize.INTEGER + }, + token: { + type: Sequelize.STRING + }, + createdAt: { + allowNull: false, + type: Sequelize.DATE + }, + updatedAt: { + allowNull: false, + type: Sequelize.DATE + } + }); + }, + down: (queryInterface, Sequelize) => { + return queryInterface.dropTable('auths'); + } +}; \ No newline at end of file diff --git a/migrations/20180209140509-create-forgotpassword.js b/migrations/20180209140509-create-forgotpassword.js new file mode 100644 index 0000000..bf5ecce --- /dev/null +++ b/migrations/20180209140509-create-forgotpassword.js @@ -0,0 +1,29 @@ + +module.exports = { + up: (queryInterface, Sequelize) => queryInterface.createTable('forgotpasswords', { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: Sequelize.INTEGER, + }, + userId: { + type: Sequelize.INTEGER, + }, + otp: { + type: Sequelize.INTEGER, + }, + timestamp: { + type: Sequelize.STRING, + }, + createdAt: { + allowNull: false, + type: Sequelize.DATE, + }, + updatedAt: { + allowNull: false, + type: Sequelize.DATE, + }, + }), + down: (queryInterface, Sequelize) => queryInterface.dropTable('forgotpasswords'), +}; diff --git a/migrations/20180223033914-create-contacts.js b/migrations/20180223033914-create-contacts.js new file mode 100644 index 0000000..7617b0f --- /dev/null +++ b/migrations/20180223033914-create-contacts.js @@ -0,0 +1,30 @@ +'use strict'; +module.exports = { + up: (queryInterface, Sequelize) => { + return queryInterface.createTable('contacts', { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: Sequelize.INTEGER + }, + userId: { + type: Sequelize.INTEGER + }, + friendId: { + type: Sequelize.INTEGER + }, + createdAt: { + allowNull: false, + type: Sequelize.DATE + }, + updatedAt: { + allowNull: false, + type: Sequelize.DATE + } + }); + }, + down: (queryInterface, Sequelize) => { + return queryInterface.dropTable('contacts'); + } +}; \ No newline at end of file diff --git a/migrations/20180223063545-changetype-transaction-id.js b/migrations/20180223063545-changetype-transaction-id.js new file mode 100644 index 0000000..a2119ad --- /dev/null +++ b/migrations/20180223063545-changetype-transaction-id.js @@ -0,0 +1,23 @@ + + +module.exports = { + up: (queryInterface, Sequelize) => { + queryInterface.changeColumn( + 'transactions', + 'transactionId', + { + type: Sequelize.STRING, + }, + ); + }, + + down: (queryInterface, Sequelize) => { + queryInterface.changeColumn( + 'transactions', + 'transactionId', + { + type: Sequelize.STRING, + }, + ); + }, +}; diff --git a/models/auths.js b/models/auths.js new file mode 100644 index 0000000..81a740a --- /dev/null +++ b/models/auths.js @@ -0,0 +1,14 @@ + + +module.exports = (sequelize, DataTypes) => { + const auths = sequelize.define('auths', { + token: DataTypes.STRING, + }, { + classMethods: { + associate(models) { + // associations can be defined here + }, + }, + }); + return auths; +}; diff --git a/models/contacts.js b/models/contacts.js new file mode 100644 index 0000000..3d04b81 --- /dev/null +++ b/models/contacts.js @@ -0,0 +1,11 @@ +'use strict'; +module.exports = (sequelize, DataTypes) => { + var contacts = sequelize.define('contacts', { + userId: DataTypes.INTEGER, + friendId: DataTypes.INTEGER + }, {}); + contacts.associate = function(models) { + // associations can be defined here + }; + return contacts; +}; \ No newline at end of file diff --git a/models/forgotpasswords.js b/models/forgotpasswords.js new file mode 100644 index 0000000..e9d0035 --- /dev/null +++ b/models/forgotpasswords.js @@ -0,0 +1,15 @@ + +module.exports = (sequelize, DataTypes) => { + const forgotpasswords = sequelize.define('forgotpasswords', { + userId: DataTypes.INTEGER, + otp: DataTypes.INTEGER, + timestamp: DataTypes.STRING, + }, { + classMethods: { + associate(models) { + // associations can be defined here + }, + }, + }); + return forgotpasswords; +}; diff --git a/package-lock.json b/package-lock.json index 7a38835..c4f320a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -92,7 +92,6 @@ "version": "5.5.2", "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", - "dev": true, "requires": { "co": "4.6.0", "fast-deep-equal": "1.0.0", @@ -158,14 +157,12 @@ "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true + "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=", - "dev": true + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" }, "anymatch": { "version": "1.3.2", @@ -194,6 +191,16 @@ "sprintf-js": "1.0.3" } }, + "aria-query": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-0.7.1.tgz", + "integrity": "sha1-Jsu1r/ZBRLCoJb4YRuCxbPoAsR4=", + "dev": true, + "requires": { + "ast-types-flow": "0.0.7", + "commander": "2.14.0" + } + }, "arr-diff": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", @@ -221,6 +228,16 @@ "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=", "dev": true }, + "array-includes": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.0.3.tgz", + "integrity": "sha1-GEtI9i2S10UrsxsyMWXH+L0CJm0=", + "dev": true, + "requires": { + "define-properties": "1.1.2", + "es-abstract": "1.10.0" + } + }, "array-union": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", @@ -248,17 +265,20 @@ "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", "dev": true }, + "asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" + }, "asn1": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", - "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=", - "dev": true + "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=" }, "assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" }, "assign-symbols": { "version": "1.0.0", @@ -266,6 +286,12 @@ "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", "dev": true }, + "ast-types-flow": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", + "integrity": "sha1-9wtzXGvKGlycItmCw+Oef+ujva0=", + "dev": true + }, "astral-regex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", @@ -276,7 +302,6 @@ "version": "2.6.0", "resolved": "https://registry.npmjs.org/async/-/async-2.6.0.tgz", "integrity": "sha512-xAfGg1/NTLBBKlHFmnd7PlmUW9KhVQIUuSrYem9xzFUZy13ScvtyGGejaae9iAVRiRq9+Cx7DPFaAAhCpyxyPw==", - "dev": true, "requires": { "lodash": "4.17.5" } @@ -296,8 +321,7 @@ "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "dev": true + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, "atob": { "version": "2.0.3", @@ -308,14 +332,21 @@ "aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", - "dev": true + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" }, "aws4": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", - "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=", - "dev": true + "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=" + }, + "axobject-query": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-0.1.0.tgz", + "integrity": "sha1-YvWdvFnJ+SQnWco0mWDnov48NsA=", + "dev": true, + "requires": { + "ast-types-flow": "0.0.7" + } }, "b64": { "version": "3.0.3", @@ -626,7 +657,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", - "dev": true, "optional": true, "requires": { "tweetnacl": "0.14.5" @@ -643,6 +673,34 @@ "integrity": "sha1-RqoXUftqL5PuXmibsQh9SxTGwgU=", "dev": true }, + "bl": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/bl/-/bl-1.1.2.tgz", + "integrity": "sha1-/cqHGplxOqANGeO7ukHER4emU5g=", + "requires": { + "readable-stream": "2.0.6" + }, + "dependencies": { + "readable-stream": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", + "integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=", + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "1.0.7", + "string_decoder": "0.10.31", + "util-deprecate": "1.0.2" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + } + } + }, "bluebird": { "version": "3.5.1", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", @@ -834,8 +892,7 @@ "caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", - "dev": true + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" }, "catbox": { "version": "7.1.5", @@ -1411,8 +1468,7 @@ "co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", - "dev": true + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" }, "code-point-at": { "version": "1.1.0", @@ -1449,7 +1505,6 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=", - "dev": true, "requires": { "delayed-stream": "1.0.0" } @@ -1553,11 +1608,16 @@ "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", "dev": true }, + "core-js": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz", + "integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=", + "dev": true + }, "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=", - "dev": true + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, "create-error-class": { "version": "3.0.2", @@ -1627,11 +1687,16 @@ "es5-ext": "0.10.38" } }, + "damerau-levenshtein": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.4.tgz", + "integrity": "sha1-AxkcQyy27qFou3fzpV/9zLiXhRQ=", + "dev": true + }, "dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "dev": true, "requires": { "assert-plus": "1.0.0" } @@ -1733,14 +1798,18 @@ "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "dev": true + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" }, "depd": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" }, + "deprecate": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/deprecate/-/deprecate-1.0.0.tgz", + "integrity": "sha1-ZhSQ7SQokWpsiIPYg05WRvTkpKg=" + }, "detect-indent": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz", @@ -1811,7 +1880,6 @@ "version": "0.1.1", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", - "dev": true, "optional": true, "requires": { "jsbn": "0.1.1" @@ -1850,6 +1918,21 @@ } } }, + "emoji-regex": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-6.5.1.tgz", + "integrity": "sha512-PAHp6TxrCy7MGMFidro8uikr+zlJJKJ/Q6mm2ExZ7HwkyR9lSVFfE3kt36qcwa24BQL7y0G9axycGjK1A/0uNQ==", + "dev": true + }, + "encoding": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz", + "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=", + "dev": true, + "requires": { + "iconv-lite": "0.4.19" + } + }, "error-ex": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz", @@ -1934,8 +2017,7 @@ "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=", - "dev": true + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, "escodegen": { "version": "1.9.0", @@ -2079,6 +2161,44 @@ "read-pkg-up": "2.0.0" } }, + "eslint-plugin-jsx-a11y": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.0.3.tgz", + "integrity": "sha1-VFg9GuRCSDFi4EDhPMMYZUZRAOU=", + "dev": true, + "requires": { + "aria-query": "0.7.1", + "array-includes": "3.0.3", + "ast-types-flow": "0.0.7", + "axobject-query": "0.1.0", + "damerau-levenshtein": "1.0.4", + "emoji-regex": "6.5.1", + "jsx-ast-utils": "2.0.1" + } + }, + "eslint-plugin-react": { + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.7.0.tgz", + "integrity": "sha512-KC7Snr4YsWZD5flu6A5c0AcIZidzW3Exbqp7OT67OaD2AppJtlBr/GuPrW/vaQM/yfZotEvKAdrxrO+v8vwYJA==", + "dev": true, + "requires": { + "doctrine": "2.1.0", + "has": "1.0.1", + "jsx-ast-utils": "2.0.1", + "prop-types": "15.6.0" + }, + "dependencies": { + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "requires": { + "esutils": "2.0.2" + } + } + } + }, "eslint-restricted-globals": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/eslint-restricted-globals/-/eslint-restricted-globals-0.1.1.tgz", @@ -2259,8 +2379,7 @@ "extend": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", - "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=", - "dev": true + "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=" }, "extend-shallow": { "version": "3.0.2", @@ -2306,20 +2425,17 @@ "extsprintf": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", - "dev": true + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" }, "fast-deep-equal": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz", - "integrity": "sha1-liVqO8l1WV6zbYLpkp0GDYk0Of8=", - "dev": true + "integrity": "sha1-liVqO8l1WV6zbYLpkp0GDYk0Of8=" }, "fast-json-stable-stringify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", - "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", - "dev": true + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" }, "fast-levenshtein": { "version": "2.0.6", @@ -2341,6 +2457,21 @@ "bser": "2.0.0" } }, + "fbjs": { + "version": "0.8.16", + "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.16.tgz", + "integrity": "sha1-XmdDL1UNxBtXK/VYR7ispk5TN9s=", + "dev": true, + "requires": { + "core-js": "1.2.7", + "isomorphic-fetch": "2.2.1", + "loose-envify": "1.3.1", + "object-assign": "4.1.1", + "promise": "7.3.1", + "setimmediate": "1.0.5", + "ua-parser-js": "0.7.17" + } + }, "figures": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", @@ -2435,14 +2566,12 @@ "forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", - "dev": true + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" }, "form-data": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.1.tgz", "integrity": "sha1-b7lPvXGIUwbXPRXMSX/kzE7NRL8=", - "dev": true, "requires": { "asynckit": "0.4.0", "combined-stream": "1.0.5", @@ -2499,13 +2628,15 @@ "dependencies": { "abbrev": { "version": "1.1.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.0.tgz", + "integrity": "sha1-0FVMIlZjbi9W58LlrRg/hZQo2B8=", "dev": true, "optional": true }, "ajv": { "version": "4.11.8", - "bundled": true, + "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", + "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", "dev": true, "optional": true, "requires": { @@ -2515,18 +2646,21 @@ }, "ansi-regex": { "version": "2.1.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", "dev": true }, "aproba": { "version": "1.1.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.1.1.tgz", + "integrity": "sha1-ldNgDwdxCqDpKYxyatXs8urLq6s=", "dev": true, "optional": true }, "are-we-there-yet": { "version": "1.1.4", - "bundled": true, + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz", + "integrity": "sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0=", "dev": true, "optional": true, "requires": { @@ -2536,42 +2670,49 @@ }, "asn1": { "version": "0.2.3", - "bundled": true, + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", + "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=", "dev": true, "optional": true }, "assert-plus": { "version": "0.2.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz", + "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=", "dev": true, "optional": true }, "asynckit": { "version": "0.4.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", "dev": true, "optional": true }, "aws-sign2": { "version": "0.6.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz", + "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=", "dev": true, "optional": true }, "aws4": { "version": "1.6.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", + "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=", "dev": true, "optional": true }, "balanced-match": { "version": "0.4.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz", + "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg=", "dev": true }, "bcrypt-pbkdf": { "version": "1.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", + "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", "dev": true, "optional": true, "requires": { @@ -2580,7 +2721,8 @@ }, "block-stream": { "version": "0.0.9", - "bundled": true, + "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", + "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=", "dev": true, "requires": { "inherits": "2.0.3" @@ -2588,7 +2730,8 @@ }, "boom": { "version": "2.10.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", + "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=", "dev": true, "requires": { "hoek": "2.16.3" @@ -2596,7 +2739,8 @@ }, "brace-expansion": { "version": "1.1.7", - "bundled": true, + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.7.tgz", + "integrity": "sha1-Pv/DxQ4ABTH7cg6v+A8K6O8jz1k=", "dev": true, "requires": { "balanced-match": "0.4.2", @@ -2605,29 +2749,34 @@ }, "buffer-shims": { "version": "1.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/buffer-shims/-/buffer-shims-1.0.0.tgz", + "integrity": "sha1-mXjOMXOIxkmth5MCjDR37wRKi1E=", "dev": true }, "caseless": { "version": "0.12.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", "dev": true, "optional": true }, "co": { "version": "4.6.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", "dev": true, "optional": true }, "code-point-at": { "version": "1.1.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", "dev": true }, "combined-stream": { "version": "1.0.5", - "bundled": true, + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", + "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=", "dev": true, "requires": { "delayed-stream": "1.0.0" @@ -2635,22 +2784,26 @@ }, "concat-map": { "version": "0.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true }, "console-control-strings": { "version": "1.1.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", "dev": true }, "core-util-is": { "version": "1.0.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", "dev": true }, "cryptiles": { "version": "2.0.5", - "bundled": true, + "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", + "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=", "dev": true, "requires": { "boom": "2.10.1" @@ -2658,7 +2811,8 @@ }, "dashdash": { "version": "1.14.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", "dev": true, "optional": true, "requires": { @@ -2667,7 +2821,8 @@ "dependencies": { "assert-plus": { "version": "1.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", "dev": true, "optional": true } @@ -2675,7 +2830,8 @@ }, "debug": { "version": "2.6.8", - "bundled": true, + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", + "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=", "dev": true, "optional": true, "requires": { @@ -2684,30 +2840,35 @@ }, "deep-extend": { "version": "0.4.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.2.tgz", + "integrity": "sha1-SLaZwn4zS/ifEIkr5DL25MfTSn8=", "dev": true, "optional": true }, "delayed-stream": { "version": "1.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", "dev": true }, "delegates": { "version": "1.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", "dev": true, "optional": true }, "detect-libc": { "version": "1.0.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.2.tgz", + "integrity": "sha1-ca1dIEvxempsqPRQxhRUBm70YeE=", "dev": true, "optional": true }, "ecc-jsbn": { "version": "0.1.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", + "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", "dev": true, "optional": true, "requires": { @@ -2716,24 +2877,28 @@ }, "extend": { "version": "3.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", + "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=", "dev": true, "optional": true }, "extsprintf": { "version": "1.0.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.0.2.tgz", + "integrity": "sha1-4QgOBljjALBilJkMxw4VAiNf1VA=", "dev": true }, "forever-agent": { "version": "0.6.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", "dev": true, "optional": true }, "form-data": { "version": "2.1.4", - "bundled": true, + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz", + "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=", "dev": true, "optional": true, "requires": { @@ -2744,12 +2909,14 @@ }, "fs.realpath": { "version": "1.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true }, "fstream": { "version": "1.0.11", - "bundled": true, + "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz", + "integrity": "sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=", "dev": true, "requires": { "graceful-fs": "4.1.11", @@ -2760,7 +2927,8 @@ }, "fstream-ignore": { "version": "1.0.5", - "bundled": true, + "resolved": "https://registry.npmjs.org/fstream-ignore/-/fstream-ignore-1.0.5.tgz", + "integrity": "sha1-nDHa40dnAY/h0kmyTa2mfQktoQU=", "dev": true, "optional": true, "requires": { @@ -2771,7 +2939,8 @@ }, "gauge": { "version": "2.7.4", - "bundled": true, + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", "dev": true, "optional": true, "requires": { @@ -2787,7 +2956,8 @@ }, "getpass": { "version": "0.1.7", - "bundled": true, + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", "dev": true, "optional": true, "requires": { @@ -2796,7 +2966,8 @@ "dependencies": { "assert-plus": { "version": "1.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", "dev": true, "optional": true } @@ -2804,7 +2975,8 @@ }, "glob": { "version": "7.1.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", "dev": true, "requires": { "fs.realpath": "1.0.0", @@ -2817,18 +2989,21 @@ }, "graceful-fs": { "version": "4.1.11", - "bundled": true, + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", "dev": true }, "har-schema": { "version": "1.0.5", - "bundled": true, + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-1.0.5.tgz", + "integrity": "sha1-0mMTX0MwfALGAq/I/pWXDAFRNp4=", "dev": true, "optional": true }, "har-validator": { "version": "4.2.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-4.2.1.tgz", + "integrity": "sha1-M0gdDxu/9gDdID11gSpqX7oALio=", "dev": true, "optional": true, "requires": { @@ -2838,13 +3013,15 @@ }, "has-unicode": { "version": "2.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", "dev": true, "optional": true }, "hawk": { "version": "3.1.3", - "bundled": true, + "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", + "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=", "dev": true, "requires": { "boom": "2.10.1", @@ -2855,12 +3032,14 @@ }, "hoek": { "version": "2.16.3", - "bundled": true, + "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", + "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=", "dev": true }, "http-signature": { "version": "1.1.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", + "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=", "dev": true, "optional": true, "requires": { @@ -2871,7 +3050,8 @@ }, "inflight": { "version": "1.0.6", - "bundled": true, + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "dev": true, "requires": { "once": "1.4.0", @@ -2880,18 +3060,21 @@ }, "inherits": { "version": "2.0.3", - "bundled": true, + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", "dev": true }, "ini": { "version": "1.3.4", - "bundled": true, + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.4.tgz", + "integrity": "sha1-BTfLedr1m1mhpRff9wbIbsA5Fi4=", "dev": true, "optional": true }, "is-fullwidth-code-point": { "version": "1.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "dev": true, "requires": { "number-is-nan": "1.0.1" @@ -2899,24 +3082,28 @@ }, "is-typedarray": { "version": "1.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", "dev": true, "optional": true }, "isarray": { "version": "1.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", "dev": true }, "isstream": { "version": "0.1.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", "dev": true, "optional": true }, "jodid25519": { "version": "1.0.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/jodid25519/-/jodid25519-1.0.2.tgz", + "integrity": "sha1-BtSRIlUJNBlHfUJWM2BuDpB4KWc=", "dev": true, "optional": true, "requires": { @@ -2925,19 +3112,22 @@ }, "jsbn": { "version": "0.1.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", "dev": true, "optional": true }, "json-schema": { "version": "0.2.3", - "bundled": true, + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", "dev": true, "optional": true }, "json-stable-stringify": { "version": "1.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", + "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", "dev": true, "optional": true, "requires": { @@ -2946,19 +3136,22 @@ }, "json-stringify-safe": { "version": "5.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", "dev": true, "optional": true }, "jsonify": { "version": "0.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", + "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", "dev": true, "optional": true }, "jsprim": { "version": "1.4.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.0.tgz", + "integrity": "sha1-o7h+QCmNjDgFUtjMdiigu5WiKRg=", "dev": true, "optional": true, "requires": { @@ -2970,7 +3163,8 @@ "dependencies": { "assert-plus": { "version": "1.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", "dev": true, "optional": true } @@ -2978,12 +3172,14 @@ }, "mime-db": { "version": "1.27.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.27.0.tgz", + "integrity": "sha1-gg9XIpa70g7CXtVeW13oaeVDbrE=", "dev": true }, "mime-types": { "version": "2.1.15", - "bundled": true, + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.15.tgz", + "integrity": "sha1-pOv1BkCUVpI3uM9wBGd20J/JKu0=", "dev": true, "requires": { "mime-db": "1.27.0" @@ -2991,7 +3187,8 @@ }, "minimatch": { "version": "3.0.4", - "bundled": true, + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "dev": true, "requires": { "brace-expansion": "1.1.7" @@ -2999,12 +3196,14 @@ }, "minimist": { "version": "0.0.8", - "bundled": true, + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", "dev": true }, "mkdirp": { "version": "0.5.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "dev": true, "requires": { "minimist": "0.0.8" @@ -3012,13 +3211,15 @@ }, "ms": { "version": "2.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true, "optional": true }, "node-pre-gyp": { "version": "0.6.39", - "bundled": true, + "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.6.39.tgz", + "integrity": "sha512-OsJV74qxnvz/AMGgcfZoDaeDXKD3oY3QVIbBmwszTFkRisTSXbMQyn4UWzUMOtA5SVhrBZOTp0wcoSBgfMfMmQ==", "dev": true, "optional": true, "requires": { @@ -3037,7 +3238,8 @@ }, "nopt": { "version": "4.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", + "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", "dev": true, "optional": true, "requires": { @@ -3047,7 +3249,8 @@ }, "npmlog": { "version": "4.1.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.0.tgz", + "integrity": "sha512-ocolIkZYZt8UveuiDS0yAkkIjid1o7lPG8cYm05yNYzBn8ykQtaiPMEGp8fY9tKdDgm8okpdKzkvu1y9hUYugA==", "dev": true, "optional": true, "requires": { @@ -3059,24 +3262,28 @@ }, "number-is-nan": { "version": "1.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", "dev": true }, "oauth-sign": { "version": "0.8.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", + "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=", "dev": true, "optional": true }, "object-assign": { "version": "4.1.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", "dev": true, "optional": true }, "once": { "version": "1.4.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, "requires": { "wrappy": "1.0.2" @@ -3084,19 +3291,22 @@ }, "os-homedir": { "version": "1.0.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", "dev": true, "optional": true }, "os-tmpdir": { "version": "1.0.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", "dev": true, "optional": true }, "osenv": { "version": "0.1.4", - "bundled": true, + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.4.tgz", + "integrity": "sha1-Qv5tWVPfBsgGS+bxdsPQWqqjRkQ=", "dev": true, "optional": true, "requires": { @@ -3106,35 +3316,41 @@ }, "path-is-absolute": { "version": "1.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "dev": true }, "performance-now": { "version": "0.2.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz", + "integrity": "sha1-M+8wxcd9TqIcWlOGnZG1bY8lVeU=", "dev": true, "optional": true }, "process-nextick-args": { "version": "1.0.7", - "bundled": true, + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", + "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", "dev": true }, "punycode": { "version": "1.4.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", "dev": true, "optional": true }, "qs": { "version": "6.4.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz", + "integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM=", "dev": true, "optional": true }, "rc": { "version": "1.2.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.1.tgz", + "integrity": "sha1-LgPo5C7kULjLPc5lvhv4l04d/ZU=", "dev": true, "optional": true, "requires": { @@ -3146,7 +3362,8 @@ "dependencies": { "minimist": { "version": "1.2.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true, "optional": true } @@ -3154,7 +3371,8 @@ }, "readable-stream": { "version": "2.2.9", - "bundled": true, + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.9.tgz", + "integrity": "sha1-z3jsb0ptHrQ9JkiMrJfwQudLf8g=", "dev": true, "requires": { "buffer-shims": "1.0.0", @@ -3168,7 +3386,8 @@ }, "request": { "version": "2.81.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/request/-/request-2.81.0.tgz", + "integrity": "sha1-xpKJRqDgbF+Nb4qTM0af/aRimKA=", "dev": true, "optional": true, "requires": { @@ -3198,7 +3417,8 @@ }, "rimraf": { "version": "2.6.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.1.tgz", + "integrity": "sha1-wjOOxkPfeht/5cVPqG9XQopV8z0=", "dev": true, "requires": { "glob": "7.1.2" @@ -3206,30 +3426,35 @@ }, "safe-buffer": { "version": "5.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.0.1.tgz", + "integrity": "sha1-0mPKVGls2KMGtcplUekt5XkY++c=", "dev": true }, "semver": { "version": "5.3.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", + "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=", "dev": true, "optional": true }, "set-blocking": { "version": "2.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "dev": true, "optional": true }, "signal-exit": { "version": "3.0.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", "dev": true, "optional": true }, "sntp": { "version": "1.0.9", - "bundled": true, + "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", + "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=", "dev": true, "requires": { "hoek": "2.16.3" @@ -3237,7 +3462,8 @@ }, "sshpk": { "version": "1.13.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.0.tgz", + "integrity": "sha1-/yo+T9BEl1Vf7Zezmg/YL6+zozw=", "dev": true, "optional": true, "requires": { @@ -3254,7 +3480,8 @@ "dependencies": { "assert-plus": { "version": "1.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", "dev": true, "optional": true } @@ -3262,7 +3489,8 @@ }, "string-width": { "version": "1.0.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "dev": true, "requires": { "code-point-at": "1.1.0", @@ -3272,7 +3500,8 @@ }, "string_decoder": { "version": "1.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.1.tgz", + "integrity": "sha1-YuIA8DmVWmgQ2N8KM//A8BNmLZg=", "dev": true, "requires": { "safe-buffer": "5.0.1" @@ -3280,13 +3509,15 @@ }, "stringstream": { "version": "0.0.5", - "bundled": true, + "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", + "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=", "dev": true, "optional": true }, "strip-ansi": { "version": "3.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { "ansi-regex": "2.1.1" @@ -3294,13 +3525,15 @@ }, "strip-json-comments": { "version": "2.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", "dev": true, "optional": true }, "tar": { "version": "2.2.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz", + "integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=", "dev": true, "requires": { "block-stream": "0.0.9", @@ -3310,7 +3543,8 @@ }, "tar-pack": { "version": "3.4.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/tar-pack/-/tar-pack-3.4.0.tgz", + "integrity": "sha1-I74tf2cagzk3bL2wuP4/3r8xeYQ=", "dev": true, "optional": true, "requires": { @@ -3326,7 +3560,8 @@ }, "tough-cookie": { "version": "2.3.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.2.tgz", + "integrity": "sha1-8IH3bkyFcg5sN6X6ztc3FQ2EByo=", "dev": true, "optional": true, "requires": { @@ -3335,7 +3570,8 @@ }, "tunnel-agent": { "version": "0.6.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", "dev": true, "optional": true, "requires": { @@ -3344,30 +3580,35 @@ }, "tweetnacl": { "version": "0.14.5", - "bundled": true, + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", "dev": true, "optional": true }, "uid-number": { "version": "0.0.6", - "bundled": true, + "resolved": "https://registry.npmjs.org/uid-number/-/uid-number-0.0.6.tgz", + "integrity": "sha1-DqEOgDXo61uOREnwbaHHMGY7qoE=", "dev": true, "optional": true }, "util-deprecate": { "version": "1.0.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "dev": true }, "uuid": { "version": "3.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.0.1.tgz", + "integrity": "sha1-ZUS7ot/ajBzxfmKaOjBeK7H+5sE=", "dev": true, "optional": true }, "verror": { "version": "1.3.6", - "bundled": true, + "resolved": "https://registry.npmjs.org/verror/-/verror-1.3.6.tgz", + "integrity": "sha1-z/XfEpRtKX0rqu+qJoniW+AcAFw=", "dev": true, "optional": true, "requires": { @@ -3376,7 +3617,8 @@ }, "wide-align": { "version": "1.1.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.2.tgz", + "integrity": "sha512-ijDLlyQ7s6x1JgCLur53osjm/UXUYD9+0PbYKrBsYisYXzCxN+HC3mYDNy/dWdmf3AwqwU3CXwDCvsNgGK1S0w==", "dev": true, "optional": true, "requires": { @@ -3385,7 +3627,8 @@ }, "wrappy": { "version": "1.0.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true } } @@ -3402,6 +3645,19 @@ "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", "dev": true }, + "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=", + "requires": { + "is-property": "1.0.2" + } + }, "generic-pool": { "version": "2.4.3", "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-2.4.3.tgz", @@ -3429,7 +3685,6 @@ "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "dev": true, "requires": { "assert-plus": "1.0.0" } @@ -3712,14 +3967,12 @@ "har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", - "dev": true + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" }, "har-validator": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", - "dev": true, "requires": { "ajv": "5.5.2", "har-schema": "2.0.0" @@ -3738,7 +3991,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", - "dev": true, "requires": { "ansi-regex": "2.1.1" } @@ -3813,7 +4065,6 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/hawk/-/hawk-6.0.2.tgz", "integrity": "sha512-miowhl2+U7Qle4vdLqDdPt9m09K6yZhkLDTWGoUiUzrQCn+mHHSmfJgAyGaLRZbPmTqfFFjRV1QWCW0VWUJBbQ==", - "dev": true, "requires": { "boom": "4.3.1", "cryptiles": "3.1.2", @@ -3825,7 +4076,6 @@ "version": "4.3.1", "resolved": "https://registry.npmjs.org/boom/-/boom-4.3.1.tgz", "integrity": "sha1-T4owBctKfjiJ90kDD9JbluAdLjE=", - "dev": true, "requires": { "hoek": "4.2.0" } @@ -3897,7 +4147,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", - "dev": true, "requires": { "assert-plus": "1.0.0", "jsprim": "1.4.1", @@ -4021,8 +4270,7 @@ "inherits": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" }, "ini": { "version": "1.3.5", @@ -4262,6 +4510,23 @@ "is-path-inside": "1.0.1" } }, + "is-my-ip-valid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz", + "integrity": "sha512-gmh/eWXROncUzRnIa1Ubrt5b8ep/MGSnfAUI3aRp+sqTCs1tv1Isl8d8F6JmkN3dXKc3ehZMrtiPN9eL03NuaQ==" + }, + "is-my-json-valid": { + "version": "2.17.2", + "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.17.2.tgz", + "integrity": "sha512-IBhBslgngMQN8DDSppmgDv7RNrlFotuuDsKcrCP3+HbFaVivIBU7u9oiiErw8sH4ynx3+gOGQ3q2otkgiSi6kg==", + "requires": { + "generate-function": "2.0.0", + "generate-object-property": "1.2.0", + "is-my-ip-valid": "1.0.0", + "jsonpointer": "4.0.1", + "xtend": "4.0.1" + } + }, "is-npm": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-1.0.0.tgz", @@ -4359,6 +4624,11 @@ "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", "dev": true }, + "is-property": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=" + }, "is-redirect": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz", @@ -4401,8 +4671,7 @@ "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", - "dev": true + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" }, "is-utf8": { "version": "0.2.1", @@ -4419,8 +4688,7 @@ "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, "isemail": { "version": "2.2.1", @@ -4442,11 +4710,20 @@ "isarray": "1.0.0" } }, + "isomorphic-fetch": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz", + "integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=", + "dev": true, + "requires": { + "node-fetch": "1.7.3", + "whatwg-fetch": "2.0.3" + } + }, "isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", - "dev": true + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" }, "istanbul-api": { "version": "1.2.1", @@ -4970,7 +5247,6 @@ "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "dev": true, "optional": true }, "jsdom": { @@ -5016,8 +5292,7 @@ "json-schema": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", - "dev": true + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" }, "json-schema-ref-parser": { "version": "4.1.0", @@ -5043,8 +5318,7 @@ "json-schema-traverse": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", - "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", - "dev": true + "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=" }, "json-stable-stringify": { "version": "1.0.1", @@ -5064,8 +5338,7 @@ "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=", - "dev": true + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" }, "json5": { "version": "0.5.1", @@ -5088,6 +5361,11 @@ "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", "dev": true }, + "jsonpointer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz", + "integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk=" + }, "jsonwebtoken": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.1.1.tgz", @@ -5116,7 +5394,6 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", - "dev": true, "requires": { "assert-plus": "1.0.0", "extsprintf": "1.3.0", @@ -5124,6 +5401,15 @@ "verror": "1.10.0" } }, + "jsx-ast-utils": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.0.1.tgz", + "integrity": "sha1-6AGxs5mF4g//yHtA43SAgOLcrH8=", + "dev": true, + "requires": { + "array-includes": "3.0.3" + } + }, "jwa": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.1.5.tgz", @@ -5469,7 +5755,6 @@ "version": "2.1.17", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz", "integrity": "sha1-Cdejk/A+mVp5+K+Fe3Cp4KsWVXo=", - "dev": true, "requires": { "mime-db": "1.30.0" }, @@ -5477,8 +5762,7 @@ "mime-db": { "version": "1.30.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.30.0.tgz", - "integrity": "sha1-dMZD2i3Z1qRTmZY0ZbJtXKfXHwE=", - "dev": true + "integrity": "sha1-dMZD2i3Z1qRTmZY0ZbJtXKfXHwE=" } } }, @@ -5633,6 +5917,16 @@ "vise": "2.0.2" } }, + "node-fetch": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz", + "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==", + "dev": true, + "requires": { + "encoding": "0.1.12", + "is-stream": "1.1.0" + } + }, "node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -5733,8 +6027,7 @@ "oauth-sign": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", - "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=", - "dev": true + "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=" }, "object-assign": { "version": "4.1.1", @@ -6080,8 +6373,7 @@ "performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", - "dev": true + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" }, "pez": { "version": "2.1.5", @@ -6182,14 +6474,12 @@ "pinkie": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", - "dev": true + "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=", - "dev": true, "requires": { "pinkie": "2.0.4" } @@ -6238,6 +6528,11 @@ } } }, + "pop-iterate": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pop-iterate/-/pop-iterate-1.0.1.tgz", + "integrity": "sha1-zqz9q0q/NT16DyqqLB/Hs/lBO6M=" + }, "posix-character-classes": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", @@ -6321,8 +6616,7 @@ "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=", - "dev": true + "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=" }, "progress": { "version": "2.0.0", @@ -6330,6 +6624,26 @@ "integrity": "sha1-ihvjZr+Pwj2yvSPxDG/pILQ4nR8=", "dev": true }, + "promise": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", + "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "dev": true, + "requires": { + "asap": "2.0.6" + } + }, + "prop-types": { + "version": "15.6.0", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.0.tgz", + "integrity": "sha1-zq8IMCL8RrSjX2nhPvda7Q1jmFY=", + "dev": true, + "requires": { + "fbjs": "0.8.16", + "loose-envify": "1.3.1", + "object-assign": "4.1.1" + } + }, "proto-list": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", @@ -6364,11 +6678,179 @@ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.0.tgz", "integrity": "sha1-X4Y+3Im5bbCQdLrXlHvwkFbKTn0=" }, + "pusher": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/pusher/-/pusher-1.5.1.tgz", + "integrity": "sha1-gYbPFuWxJLUdpsgwAaTDairUK0Q=", + "requires": { + "request": "2.74.0" + }, + "dependencies": { + "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=" + }, + "aws-sign2": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz", + "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=" + }, + "boom": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", + "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=", + "requires": { + "hoek": "2.16.3" + } + }, + "caseless": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz", + "integrity": "sha1-cVuW6phBWTzDMGeSP17GDr2k99c=" + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "requires": { + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" + } + }, + "cryptiles": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", + "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=", + "requires": { + "boom": "2.10.1" + } + }, + "form-data": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-1.0.1.tgz", + "integrity": "sha1-rjFduaSQf6BlUCMEpm13M0de43w=", + "requires": { + "async": "2.6.0", + "combined-stream": "1.0.5", + "mime-types": "2.1.17" + } + }, + "har-validator": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.6.tgz", + "integrity": "sha1-zcvAgYgmWtEZtqWnyKtw7s+10n0=", + "requires": { + "chalk": "1.1.3", + "commander": "2.14.0", + "is-my-json-valid": "2.17.2", + "pinkie-promise": "2.0.1" + } + }, + "hawk": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", + "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=", + "requires": { + "boom": "2.10.1", + "cryptiles": "2.0.5", + "hoek": "2.16.3", + "sntp": "1.0.9" + } + }, + "hoek": { + "version": "2.16.3", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", + "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=" + }, + "http-signature": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", + "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=", + "requires": { + "assert-plus": "0.2.0", + "jsprim": "1.4.1", + "sshpk": "1.13.1" + } + }, + "node-uuid": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz", + "integrity": "sha1-sEDrCSOWivq/jTL7HxfxFn/auQc=" + }, + "qs": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.2.3.tgz", + "integrity": "sha1-HPyyXBCpsrSDBT/zn138kjOQjP4=" + }, + "request": { + "version": "2.74.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.74.0.tgz", + "integrity": "sha1-dpPKdou7DqXIzgjAhKRe+gW4kqs=", + "requires": { + "aws-sign2": "0.6.0", + "aws4": "1.6.0", + "bl": "1.1.2", + "caseless": "0.11.0", + "combined-stream": "1.0.5", + "extend": "3.0.1", + "forever-agent": "0.6.1", + "form-data": "1.0.1", + "har-validator": "2.0.6", + "hawk": "3.1.3", + "http-signature": "1.1.1", + "is-typedarray": "1.0.0", + "isstream": "0.1.2", + "json-stringify-safe": "5.0.1", + "mime-types": "2.1.17", + "node-uuid": "1.4.8", + "oauth-sign": "0.8.2", + "qs": "6.2.3", + "stringstream": "0.0.5", + "tough-cookie": "2.3.3", + "tunnel-agent": "0.4.3" + } + }, + "sntp": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", + "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=", + "requires": { + "hoek": "2.16.3" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "2.1.1" + } + }, + "tunnel-agent": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz", + "integrity": "sha1-Y3PbdpCf5XDgjXNYM2Xtgop07us=" + } + } + }, + "q": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/q/-/q-2.0.3.tgz", + "integrity": "sha1-dbjbAlWhpa+C9Yw/Oqoe/sfQ0TQ=", + "requires": { + "asap": "2.0.6", + "pop-iterate": "1.0.1", + "weak-map": "1.0.5" + } + }, "qs": { "version": "6.5.1", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", - "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==", - "dev": true + "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==" }, "randomatic": { "version": "1.1.7", @@ -6573,7 +7055,6 @@ "version": "2.83.0", "resolved": "https://registry.npmjs.org/request/-/request-2.83.0.tgz", "integrity": "sha512-lR3gD69osqm6EYLk9wB/G1W/laGWjzH90t1vEa2xuxHD5KUrSzp9pUSfTm+YC5Nxt2T8nMPEvKlhbQayU7bgFw==", - "dev": true, "requires": { "aws-sign2": "0.7.0", "aws4": "1.6.0", @@ -6722,6 +7203,11 @@ "glob": "7.1.2" } }, + "rootpath": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/rootpath/-/rootpath-0.1.2.tgz", + "integrity": "sha1-Wzeah9ypBum5HWkKWZQ5vvJn6ms=" + }, "run-async": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", @@ -6790,6 +7276,11 @@ "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", "dev": true }, + "scmp": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/scmp/-/scmp-0.0.3.tgz", + "integrity": "sha1-NkjfLXKUZB5/eGc//CloHZutkHM=" + }, "semver": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", @@ -6983,6 +7474,12 @@ } } }, + "setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=", + "dev": true + }, "shebang-command": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", @@ -7192,7 +7689,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/sntp/-/sntp-2.1.0.tgz", "integrity": "sha512-FL1b58BDrqS3A11lJ0zEdnJ3UOKqVxawAkF3k7F0CVN7VQ34aZrV+G8BZ1WC9ZL7NyrwsW0oviwsWDgRuVYtJg==", - "dev": true, "requires": { "hoek": "4.2.0" } @@ -7285,7 +7781,6 @@ "version": "1.13.1", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz", "integrity": "sha1-US322mKHFEMW3EwY/hzx2UBzm+M=", - "dev": true, "requires": { "asn1": "0.2.3", "assert-plus": "1.0.0", @@ -7462,8 +7957,7 @@ "stringstream": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", - "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=", - "dev": true + "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=" }, "strip-ansi": { "version": "4.0.0", @@ -7525,8 +8019,7 @@ "supports-color": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" }, "swagger-methods": { "version": "1.0.4", @@ -7918,7 +8411,6 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.3.tgz", "integrity": "sha1-C2GKVWW23qkL80JdBNVe3EdadWE=", - "dev": true, "requires": { "punycode": "1.4.1" }, @@ -7926,8 +8418,7 @@ "punycode": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", - "dev": true + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" } } }, @@ -7950,7 +8441,6 @@ "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "dev": true, "requires": { "safe-buffer": "5.1.1" } @@ -7959,9 +8449,36 @@ "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", - "dev": true, "optional": true }, + "twilio": { + "version": "3.11.3", + "resolved": "https://registry.npmjs.org/twilio/-/twilio-3.11.3.tgz", + "integrity": "sha512-xGUH+SW8lBsPmTB9nNuawB8AajhevjIktD6LvESbIJ5HVeAgE44Y327AziCAT6lQr90PYB5joO5IUagH6YwzDA==", + "requires": { + "deprecate": "1.0.0", + "jsonwebtoken": "8.1.1", + "lodash": "4.0.0", + "moment": "2.19.3", + "q": "2.0.3", + "request": "2.83.0", + "rootpath": "0.1.2", + "scmp": "0.0.3", + "xmlbuilder": "9.0.1" + }, + "dependencies": { + "lodash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.0.0.tgz", + "integrity": "sha1-msQ4RMWV4o0wEIt7pYNwM5WSLfw=" + }, + "moment": { + "version": "2.19.3", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.19.3.tgz", + "integrity": "sha1-vbmdJw1tf9p4zA+6zoVeJ/59pp8=" + } + } + }, "type-check": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", @@ -7977,6 +8494,12 @@ "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", "dev": true }, + "ua-parser-js": { + "version": "0.7.17", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.17.tgz", + "integrity": "sha512-uRdSdu1oA1rncCQL7sCj8vSyZkgtL7faaw9Tc9rZ3mGgraQ7+Pdx7w5mnOSF3gw9ZNG6oc+KXfkon3bKuROm0g==", + "dev": true + }, "uglify-js": { "version": "2.8.29", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", @@ -8278,8 +8801,7 @@ "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, "util.promisify": { "version": "1.0.0", @@ -8315,7 +8837,6 @@ "version": "1.10.0", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", - "dev": true, "requires": { "assert-plus": "1.0.0", "core-util-is": "1.0.2", @@ -8398,6 +8919,11 @@ } } }, + "weak-map": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/weak-map/-/weak-map-1.0.5.tgz", + "integrity": "sha1-eWkVhNmGB/UHC9O3CkDmuyLkAes=" + }, "webidl-conversions": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", @@ -8413,6 +8939,12 @@ "iconv-lite": "0.4.19" } }, + "whatwg-fetch": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz", + "integrity": "sha1-nITsLc9oGH/wC8ZOEnS0QhduHIQ=", + "dev": true + }, "whatwg-url": { "version": "6.4.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.4.0.tgz", @@ -8577,6 +9109,11 @@ "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", "dev": true }, + "xmlbuilder": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.1.tgz", + "integrity": "sha1-kc1wiXdVNj66V8Et3uq0o0GmH2U=" + }, "xtend": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", diff --git a/package.json b/package.json index b529fc1..5e0fe47 100644 --- a/package.json +++ b/package.json @@ -23,10 +23,12 @@ }, "homepage": "https://github.com/TechUniv2018/WalletProject#readme", "devDependencies": { - "eslint": "^4.16.0", + "eslint": "^4.17.0", "eslint-config-airbnb": "^16.1.0", "eslint-config-airbnb-base": "^12.1.0", "eslint-plugin-import": "^2.8.0", + "eslint-plugin-jsx-a11y": "^6.0.3", + "eslint-plugin-react": "^7.6.1", "jest": "^22.1.4", "nodemon": "^1.15.1", "sequelize-cli": "^3.2.0" @@ -43,7 +45,9 @@ "jsonwebtoken": "^8.1.1", "jwt-decode": "^2.2.0", "pg": "^6.4.2", + "pusher": "^1.5.1", "sequelize": "^4.32.6", + "twilio": "^3.11.3", "vision": "^4.1.1" } } diff --git a/routes/Handler/approveHandler.js b/routes/Handler/approveHandler.js new file mode 100644 index 0000000..a9e446a --- /dev/null +++ b/routes/Handler/approveHandler.js @@ -0,0 +1,99 @@ +const Models = require('../../models'); + +const decreaseBalance = (Id, amt) => new Promise((resolve, reject) => { + Models.userDetails.findOne({ where: { userId: Id } }) + .then((userObject) => { + const newBalance = userObject.balance - amt; + if (newBalance >= 0) { + Models.userDetails.update( + { balance: newBalance }, + { where: { userId: Id } }, + ) + .then(() => { + resolve(); + }); + } else { + reject(new Error('Insufficient Balance')); + } + }); +}); + +const increaseBalance = (Id, amt) => new Promise((resolve) => { + Models.userDetails.findOne({ where: { userId: Id } }) + .then((userObject) => { + const newBalance = amt + userObject.balance; + Models.userDetails.update( + { balance: newBalance }, + { where: { userId: Id } }, + ) + .then(() => { + resolve(); + }); + }); +}); + +const transferMoney = transactionId => new Promise((resolve, reject) => { + Models.transactions.findOne({ where: { transactionId } }) + .then((transactionDetails) => { + const { fromId } = transactionDetails; + const { amount } = transactionDetails; + const { toId } = transactionDetails; + decreaseBalance(toId, amount).then(() => { + increaseBalance(fromId, amount).then(() => { + Models.transactions.update({ + status: 'COMPLETED', + }, { + where: { + transactionId, + }, + }).then(() => { + resolve(); + }); + }); + }).catch((err) => { + reject(new Error(err).message); + }); + }); +}); + + +const cancelTransaction = transactionId => new Promise((resolve) => { + // update transaction status in transaction history + Models.transactions.update({ + status: 'FAILED', + }, { + where: { + transactionId, + status: 'PENDING', + }, + }) + .then(() => { + resolve(); + }); +}); + + +const handlerFn = (transactionId, currentUserId, decision) => + new Promise((resolve, reject) => { + if (decision) { + transferMoney(transactionId).then(() => { + resolve(); + }).catch((err) => { + Models.transactions.update({ + status: 'FAILED', + }, { + where: { + transactionId, + }, + }).then(() => { + reject(new Error(err.message)); + }); + }); + } else { + cancelTransaction(transactionId).then(() => { + resolve(); + }); + } + }); + +module.exports = handlerFn; diff --git a/routes/approve.js b/routes/approve.js new file mode 100644 index 0000000..bfcb6d4 --- /dev/null +++ b/routes/approve.js @@ -0,0 +1,45 @@ +const approveHandler = require('./Handler/approveHandler'); +const Joi = require('joi'); +const approveSwagger = require('../swagger/routes/approve'); + +module.exports = [{ + method: 'PATCH', + path: '/transaction/approve', + config: { + auth: 'jwt', + tags: ['api'], + description: 'Handles approving a money request', + plugins: { + 'hapi-swagger': approveSwagger, + }, + validate: { + // auth: 'jwt', + headers: Joi.object({ + authorization: Joi.string(), + }).unknown(), + payload: Joi.object({ + transactionId: Joi.string().required().example('2_3_Mon Mar 12 2018 06:39:28 GMT+0530 (IST)'), + decision: Joi.string().min(2).max(3).required() + .example('NO'), + }), + + }, + }, + handler: (req, reply) => { + const { transactionId } = req.payload; + const goAhead = req.payload.decision; + const currentUserId = req.auth.credentials.userId; + if (goAhead === 'NO') { + approveHandler(transactionId, currentUserId, 0).then(() => { + reply('Transaction cancelled'); + }); + } else { + approveHandler(transactionId, currentUserId, 1).then(() => { + reply('Amount transferred'); + }).catch(() => { + reply('insufficient balance'); + }); + } + }, + +}]; diff --git a/routes/category.js b/routes/category.js new file mode 100644 index 0000000..8cb89f5 --- /dev/null +++ b/routes/category.js @@ -0,0 +1,75 @@ +const models = require('../models'); + +// const historySwagger = require('../swagger/routes/history'); +const historyHeaderValidation = require('../validations/routes/history'); + +const Joi = require('joi'); + + +const setCategory = (uId, tId, category) => models.transactions.findOne({ + where: { + fromId: uId, + transactionId: tId, + }, +}).then((transaction) => { + if (transaction) { + return models.transactions.update({ + category, + }, { + where: { transactionId: tId }, + }); + } + return null; +}); + +module.exports = { + method: 'PATCH', + path: '/transactions/category', + config: { + auth: 'jwt', + tags: ['api'], + description: 'catagorize a transaction made by the user', + notes: 'catagorize a transaction made by the user', + plugins: { + 'hapi-swagger': { + responses: { + 200: { + description: 'Success', + schema: Joi.object({ + history: Joi.array().items(Joi.object({ + message: Joi.string().example('category set'), + })), + }).label('Result'), + }, + 400: { description: 'Bad Request' }, + 401: { description: 'Unauthorized' }, + }, + }, + }, + validate: { + headers: historyHeaderValidation, + payload: Joi.object({ + transactionId: Joi.number() + .example(12345), + category: Joi.string() + .example('movie'), + }), + }, + }, + handler: (request, response) => { + const { transactionId, category } = request.payload; + setCategory(request.auth.credentials.userId, transactionId, category).then((result) => { + if (result) { + response({ + statusCode: 200, + message: 'category set', + }); + } else { + response({ + statusCode: 400, + message: 'no transaction made by user with given transaction id found', + }).code(400); + } + }); + }, +}; diff --git a/routes/contacts.js b/routes/contacts.js new file mode 100644 index 0000000..8678d0d --- /dev/null +++ b/routes/contacts.js @@ -0,0 +1,137 @@ + + +const model = require('../models'); +const Joi = require('joi'); + +const contactSwagger = { + responses: { + 200: { + description: 'Success', + schema: Joi.object({ + message: Joi.array().example([{ userId: 1, name: 'John_Doe' }, { userId: 2, name: 'Jane_Doe' }]), + }).label('Result'), + }, + 401: { description: 'Unauthorized' }, + }, +}; + +const headerValidation = Joi.object({ + authorization: Joi.string(), +}).unknown(); + +const contactAddSwagger = { + responses: { + 200: { + description: 'Success', + schema: Joi.object({ + message: Joi.string().example('Added a friend'), + }).label('Result'), + }, + 400: { description: 'Bad Request' }, + 401: { description: 'Unauthorized' }, + }, +}; + +const contactDeleteSwagger = { + responses: { + 200: { + description: 'Success', + schema: Joi.object({ + message: Joi.string().example('successfully removed'), + }).label('Result'), + }, + 400: { description: 'Bad Request' }, + 401: { description: 'Unauthorized' }, + }, +}; + +const contactAddValidation = Joi.object({ + friendId: Joi.number().example(2), +}); + +module.exports = [{ + method: 'GET', + path: '/contacts/getAllContacts', + config: { + tags: ['api'], + description: 'get contacts of the users', + notes: 'find all the contacts the user has added', + plugins: { + 'hapi-swagger': contactSwagger, + }, + validate: { headers: headerValidation }, + }, + handler: (request, reply) => { + model.contacts.findAll({ + where: + { userId: (request.auth.credentials.userId) }, + }).then((result) => { + const resultArrPromise = []; + result.forEach(({ friendId }) => { + resultArrPromise.push(model.users.findOne({ where: { userId: friendId } })); + }); + + const detailsArr = []; + Promise.all(resultArrPromise).then((resultArr) => { + resultArr.forEach(({ userName, userId }) => { + detailsArr.push({ + name: userName, + id: userId, + }); + }); + + reply(detailsArr); + }); + }); + }, +}, { + method: 'POST', + path: '/contacts/addContact', + config: { + tags: ['api'], + description: 'add a contact for the current user', + notes: 'insert into database the id of the contact', + plugins: { + 'hapi-swagger': contactAddSwagger, + }, + validate: { headers: headerValidation }, + }, + handler: (request, reply) => { + const { contact } = request.payload; + const { userId } = request.auth.credentials; + console.log('yupp coming inside handler'); + + model.users.findOne({ where: { userName: contact } }).then((result) => { + if (result === null) { reply({ message: 'User doesn\'t exist' }); } else if (userId === result.userId) { + reply({ message: 'Can\'t add yourself' }).code(400); + } else { + const friendId = result.userId; + model.contacts.findOrCreate({ where: { userId, friendId }, defaults: { userId, friendId } }).then(() => reply({ message: 'Successfully added' }).code(200)); + } + }); + }, +}, { + method: 'DELETE', + path: '/contacts', + config: { + tags: ['api'], + description: 'delete existing contact for the current user', + notes: 'deletes from database the id of the contact', + plugins: { + 'hapi-swagger': contactDeleteSwagger, + }, + validate: { headers: headerValidation, payload: contactAddValidation }, + }, + handler: (request, reply) => { + const { friendId } = request.payload; + const { userId } = request.auth.credentials; + + model.contacts.destroy({ where: { userId: friendId } }).then((result) => { + if (result === 0) { + reply({ message: 'User doesn\'t exist' }).code(400); + } else { + reply({ message: 'Successfully removed' }).code(200); + } + }); + }, +}]; diff --git a/routes/details.js b/routes/details.js new file mode 100644 index 0000000..1b39cfa --- /dev/null +++ b/routes/details.js @@ -0,0 +1,91 @@ +const model = require('../models'); +const Joi = require('joi'); + +const amountSwagger = { + responses: { + 200: { + description: 'Success', + schema: Joi.object({ + userId: Joi.number().example(1), + balance: Joi.number().example(10000), + }).label('Result'), + }, + 401: { description: 'Unauthorized' }, + }, +}; + +const headerValidation = Joi.object({ + authorization: Joi.string(), +}).unknown(); + +const nameSwagger = { + responses: { + 200: { + description: 'Success', + schema: Joi.object({ + userName: Joi.string().example('Bobby_B'), + }).label('Result'), + }, + 400: { description: 'Bad Request' }, + 401: { description: 'Unauthorized' }, + }, +}; + +const nameValidation = Joi.object({ + friendId: Joi.number().example(3), +}); + + +module.exports = [{ + method: 'GET', + path: '/balance', + config: { + tags: ['api'], + description: 'get money for the user', + notes: 'find the balance amount of money the user has', + plugins: { + 'hapi-swagger': amountSwagger, + }, + validate: { headers: headerValidation }, + }, + handler: (request, reply) => { + model.userDetails.findOne({ + where: + { userId: (request.auth.credentials.userId) }, + }).then((result) => { + const { userId, balance } = result; + reply({ userId, balance }); + }); + }, +}, { + method: 'POST', + path: '/userName', + config: { + tags: ['api'], + description: 'get the contact name from id', + notes: 'find the user name given the user id', + plugins: { + 'hapi-swagger': nameSwagger, + }, + validate: { headers: headerValidation, payload: nameValidation }, + }, + handler: (request, reply) => { + const { friendId } = request.payload; + const { userId } = request.auth.credentials; + + model.contacts.findOne({ where: { userId, friendId } }).then((exists) => { + if (exists !== null) { + model.userDetails.findOne({ where: { userId: friendId } }).then((result) => { + if (result === null) { + reply({ message: 'User doesn\'t exist' }).code(400); + } else { + model.users.findOne({ where: { userId: friendId } }) + .then(user => reply({ userName: user.userName }).code(200)); + } + }); + } else { + reply('Unauthorized').code(401); + } + }); + }, +}]; diff --git a/routes/forgetPassword.js b/routes/forgetPassword.js new file mode 100644 index 0000000..5c1e3ae --- /dev/null +++ b/routes/forgetPassword.js @@ -0,0 +1,146 @@ +// routed here when user forgets his password + +const sendMessage = require('./Handler/sendOTP'); +const Models = require('../models'); +const forgetPasswordSwagger = require('../swagger/routes/forgetpassword'); +const verifyOTPSwagger = require('../swagger/routes/verifyOTP'); +const Joi = require('joi'); +const bcrypt = require('bcryptjs'); + +function hashPassword(password, cb) { + bcrypt.genSalt(10, (err, salt) => { + bcrypt.hash(password, salt, (_, hash) => cb(err, hash)); + }); +} + +const getUserData = userName => new Promise((resolve) => { + // const userInfo = {};() + Models.users.findOne({ where: { userName } }) // Get userId for give username + .then((userObject) => { + Models.userDetails.findOne({ where: { userId: userObject.userId } }) + .then((item) => { + resolve({ + userId: item.userId, + userName, + phone: item.phone, + oldPassword: userObject.password, + }); + }); + }); +}); + +const otpDB = (userId, otp) => new Promise((resolve) => { + Models.forgotpasswords.create({ + userId, + otp, + timestamp: Date.now().toString(), + }).then(() => { + resolve(); + }); +}); + +const timedout = (timestamp) => { + const currentTime = Date.now(); + const timeDifference = (currentTime - timestamp) / 1000; + if (timeDifference > 300) { + return true; // request is timedout + } + + return false; // request still active +}; + +const sendOTP = (phone) => { + // send otp to mobile here + const otp = sendMessage(phone); + return otp; +}; + +const verifyOTP = (userId, otpEntry, rcvdOTP, newPassword) => new Promise((resolve) => { + if (otpEntry.otp === parseInt(rcvdOTP) && !timedout(otpEntry.timestamp)) { + // const newPassword = Math.random().toString(36).slice(-8); + // updating DB + hashPassword(newPassword, (_, hashedPassword) => { + Models.users.update( + { password: hashedPassword }, + { where: { userId } }, + ).then(() => { + resolve('Password successfully reset'); + }) + .catch((err) => { + console.log(err); + }); + }); + } else if (!timedout(otpEntry.timestamp)) { // otp is wrong + resolve('OTP is wrong, please try again'); + } else { // otp expired + resolve('Request Timed out'); + } +}); + +module.exports = [{ + method: 'POST', + path: '/forgetPassword', + config: { + auth: false, + tags: ['api'], + description: 'Registers a request for password reset', + notes: 'Sends OTP for password reset for valid userName', + plugins: { + 'hapi-swagger': forgetPasswordSwagger, + }, + validate: { + payload: Joi.object({ + userName: Joi.string().min(5).max(15).regex(/^[a-z][a-z0-9_]*$/i) + .example('John_Doe'), + userId: Joi.number().integer().example(1), + }), + }, + }, + handler: (req, reply) => { // sends OTP to user + const { userName } = req.payload; + getUserData(userName).then((userInfo) => { + sendOTP(userInfo.phone).then((otp) => { + otpDB(userInfo.userId, otp).then(() => { + reply('OTP sent on registered mobile'); + }); + }); + }); + }, +}, +{ + method: 'PATCH', + path: '/forgetPassword/verifyOTP', + config: { + auth: false, + tags: ['api'], + description: 'Verifies OTP and resets password for valid OTP', + plugins: { + 'hapi-swagger': verifyOTPSwagger, + }, + // validate: { + // payload: Joi.object({ + // otp: Joi.number().integer().min(100000).max(999999), + // newPassword: Joi.string().min(4).max(20).example('newPassword'), + // }), + // }, + }, + handler: (req, reply) => { + const rcvdOTP = req.payload.otp; + const { userName, newPassword } = req.payload; + + Models.users.findOne({ where: { userName } }).then((search) => { + const { userId } = search; + Models.forgotpasswords.findAll({ // Get latest otp info + where: { userId }, + limit: 1, + order: [['createdAt', 'DESC']], + }).then((entry) => { + const otpEntry = entry[0].dataValues; + verifyOTP(userId, otpEntry, rcvdOTP, newPassword) + .then((response) => { + reply(response); + }); + }); + }); + }, +}]; diff --git a/routes/handler/sendOTP.js b/routes/handler/sendOTP.js new file mode 100644 index 0000000..1bb0aac --- /dev/null +++ b/routes/handler/sendOTP.js @@ -0,0 +1,32 @@ +// sends OTP on user mobile number; +const Models = require('../../models'); +const Twilio = require('twilio'); + +const sendOTP = phone => new Promise((resolve) => { + const accountSid = 'AC1a67d02ea00f6a0f7f0cb7373951f48f'; // Your Account SID from www.twilio.com/console + Models.auths.findAll({ + limit: 1, + order: [['createdAt', 'DESC']], + }) + .then((entries) => { + // console.log(entries); + const authToken = entries[0].dataValues.token; + console.log('DB updated'); + const client = new Twilio(accountSid, authToken); + // TODO: Add seed for random + const otp = Math.floor(100000 + (Math.random() * 900000)); + + client.messages.create({ + body: `OTP for BATUA password reset: ${otp}`, + to: `+91${phone}`, // Text this number + from: '+19182057778', // From a valid Twilio number + }) + .then(() => { + console.log('Completed'); + resolve(otp); + }) + .catch(err => console.log(err)); + }); +}); + +module.exports = sendOTP; diff --git a/routes/history.js b/routes/history.js index e24dee6..8908f1b 100644 --- a/routes/history.js +++ b/routes/history.js @@ -6,11 +6,30 @@ const historyHeaderValidation = require('../validations/routes/history'); const getHistory = id => models.transactions.findAll({ + attributes: ['transactionId', 'fromId', 'toId', 'amount', 'reason', 'status', 'timeStamp', 'category', 'type'], where: { [Sequelize.Op.or]: [{ fromId: id }, { toId: id }], }, +}).then((result) => { + const resultArrPromise = []; + const detailsArr = []; + result.forEach((transaction) => { + detailsArr.push(transaction.get({ plain: true })); + resultArrPromise.push(models.users.findOne({ where: { userId: transaction.fromId } })); + resultArrPromise.push(models.users.findOne({ where: { userId: transaction.toId } })); + }); + + return Promise.all(resultArrPromise).then((resultArr) => { + for (let transactionNo = 0; transactionNo < result.length; transactionNo += 1) { + detailsArr[transactionNo].fromUser = resultArr[transactionNo * 2].userName; + detailsArr[transactionNo].toUser = resultArr[(transactionNo * 2) + 1].userName; + } + + return detailsArr; + }); }); + module.exports = { method: 'GET', path: '/transactions/history', diff --git a/routes/index.js b/routes/index.js index 31177a0..6c976e8 100644 --- a/routes/index.js +++ b/routes/index.js @@ -1,7 +1,31 @@ const ping = require('./ping'); + +const send = require('./send'); +const request = require('./request'); + const userRegistration = require('./userRegister'); +const forgetPassword = require('./forgetPassword'); const userLogin = require('./userLogin'); +const approve = require('./approve'); const auth = require('./auth'); const history = require('./history'); +const category = require('./category'); +const contacts = require('./contacts'); +const details = require('./details'); +const users = require('./users'); -module.exports = [].concat(ping, userRegistration, userLogin, auth, history); +module.exports = [].concat( + ping, + userRegistration, + userLogin, + auth, + history, + category, + contacts, + send, + request, + forgetPassword, + details, + approve, + users, +); diff --git a/routes/ping.js b/routes/ping.js index b4a5551..ef90b1a 100644 --- a/routes/ping.js +++ b/routes/ping.js @@ -1,3 +1,5 @@ +const pusher = require('../utils/pusher'); + module.exports = { method: 'GET', path: '/ping', @@ -5,6 +7,10 @@ module.exports = { auth: false, }, handler: (request, response) => { + pusher.trigger('my-channel', 'my-event', { + message: 'hello world', + }); response('pong'); }, + }; diff --git a/routes/request.js b/routes/request.js new file mode 100644 index 0000000..5ce998b --- /dev/null +++ b/routes/request.js @@ -0,0 +1,67 @@ +const Models = require('../models'); +const Joi = require('joi'); +const pusher = require('../utils/pusher'); + +const historyHeaderValidation = require('../validations/routes/history'); + +const requestSwagger = { + responses: { + 200: { + description: 'Success', + schema: Joi.object({ + message: Joi.string().example('request created successfully'), + }).label('Result'), + }, + 400: { description: 'Bad Request' }, + }, +}; +const requestPayloadValidation = Joi.object({ + toId: Joi.number().integer().min(1).example(1), + amount: Joi.number().integer().min(0).example(500), + reason: Joi.string().example('food'), +}); +const route = [ + { + method: 'POST', + path: '/transaction/request', + config: { + tags: ['api'], + description: 'request money', + notes: 'request money from another user', + plugins: { + 'hapi-swagger': requestSwagger, + }, + validate: { + headers: historyHeaderValidation, + payload: requestPayloadValidation, + }, + auth: 'jwt', + }, + handler: (request, response) => { + const amt = request.payload.amount; + const currentUserId = request.auth.credentials.userId; + const { reason, toId } = request.payload; + // create transaction + pusher.trigger( + 'money-channel', 'request-money', + { + to: currentUserId, from: toId, amount: amt, reason, + }, + ); + Models.transactions.create({ + transactionId: `${currentUserId}_${toId}_${new Date()}`, + fromId: currentUserId, + toId, + amount: amt, + reason, + status: 'PENDING', + timeStamp: new Date(), + createdAt: new Date(), + updatedAt: new Date(), + }).then(() => { + response({ statusCode: 201, message: 'transaction added' }); + }); + }, + }]; + +module.exports = route; diff --git a/routes/send.js b/routes/send.js new file mode 100644 index 0000000..578870b --- /dev/null +++ b/routes/send.js @@ -0,0 +1,96 @@ +const Models = require('../models'); +const Joi = require('joi'); +const pusher = require('../utils/pusher'); + +const historyHeaderValidation = require('../validations/routes/history'); + +const sendSwagger = { + responses: { + 200: { + description: 'Success', + schema: Joi.object({ + message: Joi.string().example('request created successfully'), + }).label('Result'), + }, + 400: { description: 'Bad Request' }, + }, +}; +const sendPayloadValidation = Joi.object({ + toId: Joi.number().integer().min(1).example(1), + amount: Joi.number().integer().min(0).example(500), + reason: Joi.string().example('food'), +}); +const getUserBalance = userId => new Promise((resolve) => { + Models.userDetails.findOne({ where: { userId } }) + .then((item) => { + resolve(item.balance); + }); +}); + +const route = [ + { + method: 'POST', + path: '/transaction/send', + config: { + tags: ['api'], + description: 'send money', + notes: 'send money from another user', + plugins: { + 'hapi-swagger': sendSwagger, + }, + validate: { + headers: historyHeaderValidation, + payload: sendPayloadValidation, + }, + auth: 'jwt', + }, + handler: (request, response) => { + const toId = request.payload.toId; + const amt = request.payload.amount; + const currentUserId = request.auth.credentials.userId; + const reason = request.payload.reason; + // console.log(`${toId} ${amt}${currentUserId}${reason}`); + getUserBalance(currentUserId).then((balance) => { + if (amt > balance) { + response('insufficient balance'); + } else { + const futureBalance = balance - amt; + pusher.trigger( + 'money-channel', 'send-money', + { + from: currentUserId, to: toId, amount: amt, reason, + }, + ); + // deduct balance from fromId + Models.userDetails.update( + { balance: futureBalance }, + { where: { userId: currentUserId } }, + ).then(() => Models.transactions.create({ + transactionId: `${currentUserId}_${toId}_${new Date()}`, + fromId: currentUserId, + toId, + amount: amt, + reason, + status: 'COMPLETED', + timeStamp: new Date(), + createdAt: new Date(), + updatedAt: new Date(), + })).then(() => getUserBalance(toId)) + .then((toBalance) => { + const futureToBalance = toBalance + amt; + Models.userDetails.update( + { balance: futureToBalance }, + { where: { userId: toId } }, + ); + }) + .then(() => { + response({ statusCode: 201, message: 'transaction added' }); + }); + } + }); + }, + + }, +]; + +module.exports = route; diff --git a/routes/userLogin.js b/routes/userLogin.js index bc247e6..21d4e95 100644 --- a/routes/userLogin.js +++ b/routes/userLogin.js @@ -12,7 +12,7 @@ function createToken(user) { return Jwt.sign({ userId: user.userId, userName: user.userName, - }, secret, { + }, secret.auth, { algorithm: 'HS256', expiresIn: '1h', }); diff --git a/routes/userRegister.js b/routes/userRegister.js index 8106314..f10a3cd 100644 --- a/routes/userRegister.js +++ b/routes/userRegister.js @@ -27,7 +27,9 @@ const route = [{ } = request.payload; Model.users.findOne({ - userName: request.payload.userName, + where: { + userName: request.payload.userName, + }, }) .then((user) => { if (user && user.userName === request.payload.userName) { diff --git a/routes/users.js b/routes/users.js new file mode 100644 index 0000000..77ee0fc --- /dev/null +++ b/routes/users.js @@ -0,0 +1,54 @@ +const model = require('../models'); +const Joi = require('joi'); + +const userSwagger = { + responses: { + 200: { + description: 'Success', + schema: Joi.object({ + details: Joi.object({ + firstName: Joi.string().min(3).max(15).regex(/^[a-z]+$/i) + .example('Jane'), + lastName: Joi.string().min(3).max(15).regex(/^[a-z]*$/i) + .example('Doe'), + aadharNo: Joi.number().positive().integer().example(1111111111), + phone: Joi.string().min(10).max(10).regex(/^[0-9]*$/i) + .example('2222222222'), + accountNo: Joi.string().min(10).max(20).regex(/^[0-9]*$/i) + .example('3333333333'), + userName: Joi.string().min(5).max(15).regex(/^[a-z][a-z0-9_]*$/i) + .example('Jane_Doe'), + balance: Joi.number().example(1000), + }), + }).label('Result'), + }, + 401: { description: 'Unauthorized' }, + }, +}; + +const headerValidation = Joi.object({ + authorization: Joi.string(), +}).unknown(); + +module.exports = [{ + method: 'GET', + path: '/users', + config: { + auth: 'jwt', + tags: ['api'], + description: 'get current user details', + notes: 'get current user details', + plugins: { + 'hapi-swagger': userSwagger, + }, + validate: { headers: headerValidation }, + }, + handler: (request, reply) => { + model.userDetails.findOne({ + where: + { userId: (request.auth.credentials.userId) }, + }).then((result) => { + reply(result); + }); + }, +}]; diff --git a/seeders/20180207093117-demo-userDetails.js b/seeders/20180207093117-demo-userDetails.js index 1a9ace6..2a663cd 100644 --- a/seeders/20180207093117-demo-userDetails.js +++ b/seeders/20180207093117-demo-userDetails.js @@ -1,8 +1,9 @@ module.exports = { up: (queryInterface, Sequelize) => queryInterface.bulkInsert('userDetails', [{ userId: 1, - phone: '1234567890', + phone: '8217740408', accountNo: '0987654321', + balance: 100, createdAt: new Date(), updatedAt: new Date(), }], {}), diff --git a/seeders/20180209114338-demo-auth.js b/seeders/20180209114338-demo-auth.js new file mode 100644 index 0000000..4f59494 --- /dev/null +++ b/seeders/20180209114338-demo-auth.js @@ -0,0 +1,11 @@ + + +module.exports = { + up: (queryInterface, Sequelize) => queryInterface.bulkInsert('auths', [{ + token: 'fd4ab3bca93f37d097252d290d8d0730', + createdAt: new Date(), + updatedAt: new Date(), + }], {}), + + down: (queryInterface, Sequelize) => queryInterface.bulkDelete('auths', null, {}), +}; diff --git a/seeders/20180209142908-demo-forgotpassword.js b/seeders/20180209142908-demo-forgotpassword.js new file mode 100644 index 0000000..250af64 --- /dev/null +++ b/seeders/20180209142908-demo-forgotpassword.js @@ -0,0 +1,14 @@ +const time = Date.now(); +const strTime = time.toString(); + +module.exports = { + up: (queryInterface, Sequelize) => queryInterface.bulkInsert('forgotpasswords', [{ + userId: 100, + otp: 100, + timestamp: strTime, + createdAt: new Date(), + updatedAt: new Date(), + }], {}), + + down: (queryInterface, Sequelize) => queryInterface.bulkDelete('forgotpasswords', null, {}), +}; diff --git a/seeders/20180210194308-add-aditional-users.js b/seeders/20180210194308-add-aditional-users.js index 7812c01..746fed7 100644 --- a/seeders/20180210194308-add-aditional-users.js +++ b/seeders/20180210194308-add-aditional-users.js @@ -13,6 +13,19 @@ module.exports = { password: bcrypt.hashSync('password', 10), createdAt: new Date(), updatedAt: new Date(), + }, + { + userId: 4, + userName: 'Rachel', + password: bcrypt.hashSync('password', 10), + createdAt: new Date(), + updatedAt: new Date(), + }, { + userId: 5, + userName: 'Ross', + password: bcrypt.hashSync('password', 10), + createdAt: new Date(), + updatedAt: new Date(), }], {}), down: (queryInterface, Sequelize) => queryInterface.bulkDelete('users', null, {}), diff --git a/seeders/20180210195346-add-aditional-userDetails.js b/seeders/20180210195346-add-aditional-userDetails.js index 1477e8a..97af2e7 100644 --- a/seeders/20180210195346-add-aditional-userDetails.js +++ b/seeders/20180210195346-add-aditional-userDetails.js @@ -5,6 +5,7 @@ module.exports = { accountNo: '0987654321', firstName: 'Alice', lastName: 'A', + balance: 100, createdAt: new Date(), updatedAt: new Date(), }, { @@ -13,6 +14,7 @@ module.exports = { accountNo: '0987654321', firstName: 'Bob', lastName: 'B', + balance: 100, createdAt: new Date(), updatedAt: new Date(), }], {}), diff --git a/seeders/20180210195615-demo-transactions.js b/seeders/20180210195615-demo-transactions.js index 3adb26f..9f9418c 100644 --- a/seeders/20180210195615-demo-transactions.js +++ b/seeders/20180210195615-demo-transactions.js @@ -5,7 +5,7 @@ module.exports = { toId: 2, amount: 300, reason: 'mcdonalds', - status: 'completed', + status: 'COMPLETED', timeStamp: new Date(), category: 'food', type: 1, @@ -14,28 +14,53 @@ module.exports = { }, { transactionId: 23456, fromId: 3, - toId: 1, + toId: 2, amount: 200, reason: 'black panther tickets', - status: 'pending', + status: 'PENDING', timeStamp: new Date(), category: 'movie', type: 2, createdAt: new Date(), updatedAt: new Date(), }, { - transactionId: 34567, + transactionId: 11212, fromId: 3, - toId: 2, - amount: 4000, + toId: 1, + amount: 100, reason: 'flight tickets to delhi', - status: 'declined', + status: 'PENDING', + timeStamp: new Date(), + category: 'travel', + type: 2, + createdAt: new Date(), + updatedAt: new Date(), + }, { + transactionId: 11111, + fromId: 2, + toId: 1, + amount: 100, + reason: 'flight tickets to delhi', + status: 'PENDING', + timeStamp: new Date(), + category: 'travel', + type: 2, + createdAt: new Date(), + updatedAt: new Date(), + }, { + transactionId: 17111, + fromId: 2, + toId: 1, + amount: 1000, + reason: 'borrowed money', + status: 'PENDING', timeStamp: new Date(), category: 'travel', type: 2, createdAt: new Date(), updatedAt: new Date(), - }], {}), + }, + ], {}), down: (queryInterface, Sequelize) => queryInterface.bulkDelete('transactions', null, {}), }; diff --git a/seeders/20180214174923-additional-users.js b/seeders/20180214174923-additional-users.js new file mode 100644 index 0000000..89df872 --- /dev/null +++ b/seeders/20180214174923-additional-users.js @@ -0,0 +1,25 @@ + + +module.exports = { + up: (queryInterface, Sequelize) => queryInterface.bulkInsert('userDetails', [{ + userId: 4, + phone: '1234567890', + accountNo: '0987654322', + firstName: 'Rachel', + lastName: 'A', + balance: 5000, + createdAt: new Date(), + updatedAt: new Date(), + }, { + userId: 5, + phone: '1234567890', + accountNo: '0987654322', + firstName: 'Ross', + lastName: 'G', + balance: 5000, + createdAt: new Date(), + updatedAt: new Date(), + }], {}), + + down: (queryInterface, Sequelize) => queryInterface.bulkDelete('userDetails', null, {}), +}; diff --git a/seeders/20180223055734-demo-contacts.js b/seeders/20180223055734-demo-contacts.js new file mode 100644 index 0000000..3e13d7e --- /dev/null +++ b/seeders/20180223055734-demo-contacts.js @@ -0,0 +1,34 @@ + + +module.exports = { + up: (queryInterface, Sequelize) => queryInterface.bulkInsert('contacts', [ + { + userId: 1, + friendId: 2, + createdAt: new Date(), + updatedAt: new Date(), + }, + { + userId: 1, + friendId: 3, + createdAt: new Date(), + updatedAt: new Date(), + }, { + userId: 2, + friendId: 1, + createdAt: new Date(), + updatedAt: new Date(), + }, { + userId: 2, + friendId: 3, + createdAt: new Date(), + updatedAt: new Date(), + }, { + userId: 3, + friendId: 2, + createdAt: new Date(), + updatedAt: new Date(), + }], {}), + + down: (queryInterface, Sequelize) => queryInterface.bulkDelete('contacts', null, {}), +}; diff --git a/server.js b/server.js index 5278d1e..b962614 100644 --- a/server.js +++ b/server.js @@ -1,8 +1,8 @@ const Hapi = require('hapi'); -const secret = require('./secret'); +const secret = require('./secret.js'); const Routes = require('./routes'); -const Jwt = require('hapi-auth-jwt2'); +const Jwt = require('hapi-auth-jwt2'); const validate = require('./validate'); const Inert = require('inert'); @@ -37,7 +37,7 @@ server.register([ }); server.auth.strategy('jwt', 'jwt', { - key: secret, + key: secret.auth, validateFunc: validate, verifyOptions: { algorithms: ['HS256'], @@ -55,4 +55,5 @@ if (!module.parent) { }); } + module.exports = server; diff --git a/swagger/routes/approve.js b/swagger/routes/approve.js new file mode 100644 index 0000000..2853755 --- /dev/null +++ b/swagger/routes/approve.js @@ -0,0 +1,17 @@ +const Joi = require('joi'); + +const approveMoneySwagger = { + responses: { + 200: { + description: 'Success', + schema: Joi.object({ + message: Joi.string().example('Transaction completed'), + }).label('Result'), + }, + }, + 400: { description: 'Invalid request' }, + 401: { description: 'Unauthorized' }, +}; + + +module.exports = approveMoneySwagger; diff --git a/swagger/routes/forgetpassword.js b/swagger/routes/forgetpassword.js new file mode 100644 index 0000000..8ab01b7 --- /dev/null +++ b/swagger/routes/forgetpassword.js @@ -0,0 +1,16 @@ + +const Joi = require('joi'); + +const forgetPasswordSwagger = { + responses: { + 200: { + description: 'Success', + schema: Joi.object({ + message: Joi.string().example('Password succesfully reset'), + }).label('Result'), + }, + 400: { description: 'Bad Request' }, + }, +}; + +module.exports = forgetPasswordSwagger; diff --git a/swagger/routes/receive.js b/swagger/routes/receive.js new file mode 100644 index 0000000..351ae5c --- /dev/null +++ b/swagger/routes/receive.js @@ -0,0 +1,17 @@ +const Joi = require('joi'); + +const receiveMoneySwagger = { + responses: { + 200: { + description: 'Success', + schema: Joi.object({ + message: Joi.string().example('Transaction completed'), + }).label('Result'), + }, + }, + 400: { description: 'Invalid request' }, + 401: { description: 'Unauthorized' }, +}; + + +module.exports = receiveMoneySwagger; diff --git a/swagger/routes/verifyOTP.js b/swagger/routes/verifyOTP.js new file mode 100644 index 0000000..24da654 --- /dev/null +++ b/swagger/routes/verifyOTP.js @@ -0,0 +1,16 @@ + +const Joi = require('joi'); + +const verifyOTPSwagger = { + responses: { + 200: { + description: 'Success', + schema: Joi.object({ + message: Joi.string().example('Password succesfully reset'), + }).label('Result'), + }, + 400: { description: 'Bad Request' }, + }, +}; + +module.exports = verifyOTPSwagger; diff --git a/tests/models/auths.test.js b/tests/models/auths.test.js new file mode 100644 index 0000000..deed2b1 --- /dev/null +++ b/tests/models/auths.test.js @@ -0,0 +1,19 @@ +const Models = require('../../models'); + +describe('Auths: check if data matches model', () => { + test('check if returned seeded data matches users model', () => Models.auths.findAll().then((result) => { + expect.assertions(result.length); + result.forEach((entry) => { + expect(entry.token).toBeDefined(); + }); + })); +}); + +describe('Auths: check size of seeded table', () => { + test('check if seeded auths table has size 1', () => { + expect.assertions(1); + return Models.auths.findAll().then((result) => { + expect(result.length).toBe(1); + }); + }); +}); diff --git a/tests/models/forgotPassword.test.js b/tests/models/forgotPassword.test.js new file mode 100644 index 0000000..23be50e --- /dev/null +++ b/tests/models/forgotPassword.test.js @@ -0,0 +1,21 @@ +const Models = require('../../models'); + +describe('forgotpasswords: check if data matches model', () => { + test('check if returned seeded data matches users model', () => Models.forgotpasswords.findAll().then((result) => { + expect.assertions(result.length * 3); + result.forEach((entry) => { + expect(entry.userId).toBeDefined(); + expect(entry.otp).toBeDefined(); + expect(entry.timestamp).toBeDefined(); + }); + })); +}); + +describe('forgotpasswords: check size of seeded table', () => { + test('check if seeded forgotpasswords table has size 1', () => { + expect.assertions(1); + return Models.forgotpasswords.findAll().then((result) => { + expect(result.length).toBe(1); + }); + }); +}); diff --git a/tests/models/users.test.js b/tests/models/users.test.js index f95e3a0..72deb4c 100644 --- a/tests/models/users.test.js +++ b/tests/models/users.test.js @@ -10,3 +10,12 @@ describe('users: check if data matches model', () => { }); })); }); + +describe('users: check size of seeded table', () => { + test('check if seeded users table has size 1', () => { + expect.assertions(1); + return Models.users.findAll().then((result) => { + expect(result.length).toBe(1); + }); + }); +}); diff --git a/tests/routes/approve.test.js b/tests/routes/approve.test.js new file mode 100644 index 0000000..8635865 --- /dev/null +++ b/tests/routes/approve.test.js @@ -0,0 +1,144 @@ +const Models = require('../../models'); +const server = require('../../server'); + +describe('url validation', () => { + test('Responds with 200 status code when provided credentials', (done) => { + const request = { + method: 'POST', + url: '/transaction/approve', + credentials: { + userId: 3, + userName: 'Bob_B', + }, + payload: { + transactionId: 23456, + decision: 'NO', + }, + }; + server.inject(request, (response) => { + expect(response.statusCode).toBe(200); + done(); + }); + }); + + test('Responds with 401 status code when no credentials are provided', (done) => { + const request = { + method: 'POST', + url: '/transaction/approve', + payload: { + transactionId: 23456, + decision: 'NO', + }, + }; + server.inject(request, (response) => { + expect(response.statusCode).toBe(401); + done(); + }); + }); +}); + +describe('request validation', () => { + test('rejects request if transactionId is not provided', (done) => { + const request = { + method: 'POST', + url: '/transaction/approve', + credentials: { + userId: 1, + userName: 'John_Doe', + }, + payload: { + decision: 'NO', + }, + }; + server.inject(request, (response) => { + expect(response.statusCode).toBe(400); + done(); + }); + }); + + test('rejects request if user decision is not provided', (done) => { + const request = { + method: 'POST', + url: '/transaction/approve', + credentials: { + userId: 1, + userName: 'John_Doe', + }, + payload: { + transactionId: 11111, + }, + }; + server.inject(request, (response) => { + expect(response.statusCode).toBe(400); + done(); + }); + }); +}); + +describe('functionality tests', () => { + test('Updates status when transaction is completed', (done) => { + const request = { + method: 'POST', + url: '/transaction/approve', + credentials: { + userId: 1, + userName: 'John_Doe', + }, + payload: { + transactionId: 11111, + decision: 'YES', + }, + }; + server.inject(request, () => { + Models.transactions.findOne({ transactionId: request.payload.transactionId }) + .then((row) => { + expect(row.status).toEqual('COMPLETED'); + done(); + }); + }); + }); + + test('Transaction is approved but user has insufficient balance', (done) => { + const request = { + method: 'POST', + url: '/transaction/approve', + credentials: { + userId: 1, + userName: 'John_Doe', + }, + payload: { + transactionId: 17111, + decision: 'NO', + }, + }; + server.inject(request, () => { + Models.transactions.findOne({ where: { transactionId: request.payload.transactionId } }) + .then((row) => { + expect(row.status).toEqual('FAILED'); + done(); + }); + }); + }); + + test('Updates status when transaction is failed', (done) => { + const request = { + method: 'POST', + url: '/transaction/approve', + credentials: { + userId: 1, + userName: 'John_Doe', + }, + payload: { + transactionId: 11212, + decision: 'NO', + }, + }; + server.inject(request, () => { + Models.transactions.findOne({ where: { transactionId: request.payload.transactionId } }) + .then((row) => { + expect(row.status).toEqual('FAILED'); + done(); + }); + }); + }); +}); diff --git a/tests/routes/contacts.test.js b/tests/routes/contacts.test.js new file mode 100644 index 0000000..317cd33 --- /dev/null +++ b/tests/routes/contacts.test.js @@ -0,0 +1,114 @@ +const server = require('../../server'); + +const userId = 1; +const userName = 'John_Doe'; + +describe('GET contacts list', () => { + test('should get 401 status code if passed without auth', (done) => { + const request = { + method: 'GET', + url: '/contacts', + }; + server.inject(request, (reply) => { + expect(reply.statusCode).toEqual(401); + done(); + }); + }); + + test('should get 200 status code if passed with auth', (done) => { + const request = { + method: 'GET', + url: '/contacts', + credentials: { userId, userName }, + }; + server.inject(request, (reply) => { + expect(reply.statusCode).toEqual(200); + done(); + }); + }); + + test('should get array of objects on success', (done) => { + const request = { + method: 'GET', + url: '/contacts', + credentials: { userId, userName }, + }; + server.inject(request, (reply) => { + expect.assertions(3); + expect(reply.result).toBeInstanceOf(Array); + expect(reply.result[0]).toHaveProperty('id'); + expect(reply.result[0]).toHaveProperty('name'); + done(); + }); + }); +}); + +describe('POST a new contact to the contact list', () => { + test('should get 401 status code if passed without auth', (done) => { + const request = { + method: 'POST', + url: '/contacts', + }; + server.inject(request, (reply) => { + expect(reply.statusCode).toEqual(401); + done(); + }); + }); + + test('should get 200 status code if passed with auth', (done) => { + const request = { + method: 'POST', + url: '/contacts', + credentials: { userId, userName }, + payload: { friendId: 2 }, + }; + server.inject(request, (reply) => { + expect(reply.statusCode).toEqual(200); + done(); + }); + }); + + + test('should get Successfully on adding a friend', (done) => { + const request = { + method: 'POST', + url: '/contacts', + credentials: { userId, userName }, + payload: { friendId: 2 }, + }; + server.inject(request, (reply) => { + expect(reply.result.message).toEqual('Successfully added'); + done(); + }); + }); + + test('should get error if passing user\'s own id', (done) => { + const request = { + method: 'POST', + url: '/contacts', + credentials: { userId, userName }, + payload: { friendId: userId }, + }; + server.inject(request, (reply) => { + expect.assertions(2); + expect(reply.result.message).toEqual('Can\'t add yourself'); + expect(reply.statusCode).toEqual(400); + done(); + }); + }); + + test('should get error if passsing non-existent id', (done) => { + const request = { + method: 'POST', + url: '/contacts', + credentials: { userId, userName }, + payload: { friendId: 23 }, + }; + server.inject(request, (reply) => { + expect.assertions(2); + expect(reply.result.message).toEqual('User doesn\'t exist'); + expect(reply.statusCode).toEqual(400); + done(); + }); + }); +}); diff --git a/tests/routes/forgetPassword.test.js b/tests/routes/forgetPassword.test.js new file mode 100644 index 0000000..86fefa6 --- /dev/null +++ b/tests/routes/forgetPassword.test.js @@ -0,0 +1,75 @@ +const server = require('../../server'); + +describe('check for sending OTP', () => { + test('/forgetPassword should respond with status code 200', (done) => { + const req = { + method: 'POST', + url: '/forgetPassword', + credentials: { + userId: 1, + userName: 'John_Doe', + }, + payload: JSON.stringify({ userName: 'John_Doe' }), + }; + server.inject(req, (response) => { + expect(response.statusCode).toEqual(200); + done(); + }); + }); + + test('/forgetPassword, OTP sent on mobile', (done) => { + const req = { + method: 'POST', + url: '/forgetPassword', + credentials: { + userId: 1, + userName: 'John_Doe', + }, + payload: JSON.stringify({ userName: 'John_Doe' }), + }; + server.inject(req, (response) => { + expect(response.result).toBe('OTP sent on registered mobile'); + done(); + }); + }); +}); + +describe('check for verifying OTP', () => { + test('/verifyOTP should respond with status code 200', (done) => { + const req = { + method: 'POST', + url: '/verifyOTP', + credentials: { + userId: 1, + userName: 'John_Doe', + }, + payload: JSON.stringify({ + newPassword: 'newPassword', + otp: 123456, + }), + }; + server.inject(req, (response) => { + expect(response.statusCode).toEqual(200); + done(); + }); + }); + + test('/verifyOTP, sending wrong OTP', (done) => { + const req = { + method: 'POST', + url: '/verifyOTP', + credentials: { + userId: 1, + userName: 'John_Doe', + }, + payload: JSON.stringify({ + newPassword: 'newPassword', + otp: 123456, + }), + }; + server.inject(req, (response) => { + expect(response.result).toBe('OTP is wrong, please try again'); + done(); + }); + }); +}); diff --git a/tests/routes/history.test.js b/tests/routes/history.test.js index 98f5b97..8944043 100644 --- a/tests/routes/history.test.js +++ b/tests/routes/history.test.js @@ -42,11 +42,13 @@ describe('check server response message', () => { }, (response) => { const { history } = JSON.parse(response.payload); - expect.assertions(history.length * 9); + expect.assertions(history.length * 11); history.forEach((transaction) => { expect(transaction.transactionId).toBeDefined(); expect(transaction.fromId).toBeDefined(); expect(transaction.toId).toBeDefined(); + expect(transaction.fromName).toBeDefined(); + expect(transaction.toName).toBeDefined(); expect(transaction.amount).toBeDefined(); expect(transaction.reason).toBeDefined(); expect(transaction.status).toBeDefined(); diff --git a/tests/routes/receive.test.js b/tests/routes/receive.test.js new file mode 100644 index 0000000..001b05c --- /dev/null +++ b/tests/routes/receive.test.js @@ -0,0 +1,124 @@ +const Models = require('../../models'); +const server = require('../../server'); + +describe('url validation', () => { + test('Responds with 200 status code when provided credentials', (done) => { + const request = { + method: 'POST', + url: '/transaction/approve', + credentials: { + userId: 3, + userName: 'Bob_B', + }, + payload: { + transactionId: 23456, + decision: 'NO', + }, + }; + server.inject(request, (response) => { + // console.log(response.payload); + expect(response.statusCode).toBe(200); + done(); + }); + }); + + test('Responds with 401 status code when no credentials are provided', (done) => { + const request = { + method: 'POST', + url: '/transaction/receive', + payload: { + transactionId: 23456, + decision: 'NO', + }, + }; + server.inject(request, (response) => { + expect(response.statusCode).toBe(401); + done(); + }); + }); +}); + +describe('request validation', () => { + test('rejects request if transactionId is not provided', (done) => { + const request = { + method: 'POST', + url: '/transaction/receive', + credentials: { + userId: 1, + userName: 'John_Doe', + }, + payload: { + decision: 'NO', + }, + }; + server.inject(request, (response) => { + expect(response.statusCode).toBe(400); + done(); + }); + }); + + test('rejects request if user decision is not provided', (done) => { + const request = { + method: 'POST', + url: '/transaction/receive', + credentials: { + userId: 1, + userName: 'John_Doe', + }, + payload: { + transactionId: 23456, + }, + }; + server.inject(request, (response) => { + expect(response.statusCode).toBe(400); + done(); + }); + }); +}); + +describe('functionality tests', () => { + test('Updates status when transaction is completed', (done) => { + const request = { + method: 'POST', + url: '/transaction/receive', + credentials: { + userId: 1, + userName: 'John_Doe', + }, + payload: { + transactionId: 11111, + decision: 'YES', + }, + }; + server.inject(request, () => { + Models.transactions.findOne({ where: { transactionId: request.payload.transactionId } }) + .then((row) => { + expect(row.status).toEqual('COMPLETED'); + done(); + }); + }); + }); + + test('Updates status when transaction is failed', (done) => { + const request = { + method: 'POST', + url: '/transaction/receive', + credentials: { + userId: 1, + userName: 'John_Doe', + }, + payload: { + transactionId: 11212, + decision: 'NO', + }, + }; + server.inject(request, () => { + Models.transactions.findOne({ where: { transactionId: request.payload.transactionId } }) + .then((row) => { + console.log(row); + expect(row.status).toEqual('FAILED'); + done(); + }); + }); + }); +}); diff --git a/tests/routes/request.test.js b/tests/routes/request.test.js new file mode 100644 index 0000000..88399d3 --- /dev/null +++ b/tests/routes/request.test.js @@ -0,0 +1,63 @@ +const server = require('../../server'); + +describe('request validation', () => { + test('Test for successful POST request', (done) => { + const request = { + method: 'POST', + url: '/transaction/request', + credentials: { + userId: 4, + }, + payload: JSON.stringify({ toId: 2, amount: 500, reason: 'food' }), + }; + server.inject(request, (response) => { + expect(response.result.statusCode).toBe(201); + done(); + }); + }); + + test('Test for unsuccessful POST request if toId is not a number', (done) => { + const request = { + method: 'POST', + url: '/transaction/request', + credentials: { + userId: 4, + }, + payload: JSON.stringify({ toId: 'm', amount: 500, reason: 'food' }), + }; + server.inject(request, (response) => { + expect(response.statusCode).toBe(400); + done(); + }); + }); + test('Test for unsuccessful POST request if amount is not a number', (done) => { + const request = { + method: 'POST', + url: '/transaction/request', + credentials: { + userId: 4, + }, + payload: JSON.stringify({ toId: 2, amount: '500m', reason: 'food' }), + }; + server.inject(request, (response) => { + expect(response.statusCode).toBe(400); + done(); + }); + }); + + + test('Test for unsuccessful POST request if reason is not a string', (done) => { + const request = { + method: 'POST', + url: '/transaction/request', + credentials: { + userId: 4, + }, + payload: JSON.stringify({ toId: 2, amount: 500, reason: 22 }), + }; + server.inject(request, (response) => { + expect(response.statusCode).toBe(400); + done(); + }); + }); +}); diff --git a/tests/routes/send.test.js b/tests/routes/send.test.js new file mode 100644 index 0000000..1bb2229 --- /dev/null +++ b/tests/routes/send.test.js @@ -0,0 +1,63 @@ +const server = require('../../server'); + +describe('request validation', () => { + test('Test for successful POST request', (done) => { + const request = { + method: 'POST', + url: '/transaction/send', + credentials: { + userId: 4, + }, + payload: JSON.stringify({ toId: 2, amount: 500, reason: 'food' }), + }; + server.inject(request, (response) => { + expect(response.result.statusCode).toBe(201); + done(); + }); + }); + + test('Test for unsuccessful POST request if toId is not a number', (done) => { + const request = { + method: 'POST', + url: '/transaction/send', + credentials: { + userId: 4, + }, + payload: JSON.stringify({ toId: 'm', amount: 500, reason: 'food' }), + }; + server.inject(request, (response) => { + expect(response.statusCode).toBe(400); + done(); + }); + }); + test('Test for unsuccessful POST request if amount is not a number', (done) => { + const request = { + method: 'POST', + url: '/transaction/send', + credentials: { + userId: 4, + }, + payload: JSON.stringify({ toId: 2, amount: '500m', reason: 'food' }), + }; + server.inject(request, (response) => { + expect(response.statusCode).toBe(400); + done(); + }); + }); + + + test('Test for unsuccessful POST request if reason is not a string', (done) => { + const request = { + method: 'POST', + url: '/transaction/send', + credentials: { + userId: 4, + }, + payload: JSON.stringify({ toId: 2, amount: 500, reason: 22 }), + }; + server.inject(request, (response) => { + expect(response.statusCode).toBe(400); + done(); + }); + }); +}); diff --git a/utils/pusher.js b/utils/pusher.js new file mode 100644 index 0000000..58e82f9 --- /dev/null +++ b/utils/pusher.js @@ -0,0 +1,13 @@ +const Pusher = require('pusher'); + +const secret = require('../secret'); + +const pusher = new Pusher({ + appId: '489832', + key: 'cc03634ec726b20a38bf', + secret: secret.pusher, + cluster: 'ap2', + encrypted: true, +}); + +module.exports = pusher;