diff --git a/package-lock.json b/package-lock.json index 3b453f2..cb4e81b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -265,19 +265,23 @@ "dependencies": { "abbrev": { "version": "1.1.1", - "bundled": true + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" }, "ansi-regex": { "version": "2.1.1", - "bundled": true + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" }, "aproba": { "version": "1.2.0", - "bundled": true + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" }, "are-we-there-yet": { "version": "1.1.5", - "bundled": true, + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", + "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", "requires": { "delegates": "1.0.0", "readable-stream": "2.3.5" @@ -285,11 +289,13 @@ }, "balanced-match": { "version": "1.0.0", - "bundled": true + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, "brace-expansion": { "version": "1.1.11", - "bundled": true, + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "requires": { "balanced-match": "1.0.0", "concat-map": "0.0.1" @@ -297,57 +303,69 @@ }, "chownr": { "version": "1.1.1", - "bundled": true + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.1.tgz", + "integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g==" }, "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=" }, "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=" }, "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=" }, "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=" }, "debug": { "version": "2.6.9", - "bundled": true, + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "requires": { "ms": "2.0.0" } }, "deep-extend": { "version": "0.6.0", - "bundled": true + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" }, "delegates": { "version": "1.0.0", - "bundled": true + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" }, "detect-libc": { "version": "1.0.3", - "bundled": true + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=" }, "fs-minipass": { "version": "1.2.5", - "bundled": true, + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.5.tgz", + "integrity": "sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ==", "requires": { "minipass": "2.3.4" } }, "fs.realpath": { "version": "1.0.0", - "bundled": true + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "gauge": { "version": "2.7.4", - "bundled": true, + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", "requires": { "aproba": "1.2.0", "console-control-strings": "1.1.0", @@ -361,7 +379,8 @@ }, "glob": { "version": "7.1.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", "requires": { "fs.realpath": "1.0.0", "inflight": "1.0.6", @@ -373,25 +392,29 @@ }, "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=" }, "iconv-lite": { "version": "0.4.24", - "bundled": true, + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "requires": { "safer-buffer": "2.1.2" } }, "ignore-walk": { "version": "3.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.1.tgz", + "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==", "requires": { "minimatch": "3.0.4" } }, "inflight": { "version": "1.0.6", - "bundled": true, + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "requires": { "once": "1.4.0", "wrappy": "1.0.2" @@ -399,37 +422,44 @@ }, "inherits": { "version": "2.0.3", - "bundled": true + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" }, "ini": { "version": "1.3.5", - "bundled": true + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" }, "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=", "requires": { "number-is-nan": "1.0.1" } }, "isarray": { "version": "1.0.0", - "bundled": true + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, "minimatch": { "version": "3.0.4", - "bundled": true, + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "requires": { "brace-expansion": "1.1.11" } }, "minimist": { "version": "0.0.8", - "bundled": true + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" }, "minipass": { "version": "2.3.4", - "bundled": true, + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.4.tgz", + "integrity": "sha512-mlouk1OHlaUE8Odt1drMtG1bAJA4ZA6B/ehysgV0LUIrDHdKgo1KorZq3pK0b/7Z7LJIQ12MNM6aC+Tn6lUZ5w==", "requires": { "safe-buffer": "5.1.2", "yallist": "3.0.2" @@ -437,35 +467,41 @@ "dependencies": { "safe-buffer": { "version": "5.1.2", - "bundled": true + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "yallist": { "version": "3.0.2", - "bundled": true + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.2.tgz", + "integrity": "sha1-hFK0u36Dx8GI2AQcGoN8dz1ti7k=" } } }, "minizlib": { "version": "1.1.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.1.0.tgz", + "integrity": "sha512-4T6Ur/GctZ27nHfpt9THOdRZNgyJ9FZchYO1ceg5S8Q3DNLCKYy44nCZzgCJgcvx2UM8czmqak5BCxJMrq37lA==", "requires": { "minipass": "2.3.4" } }, "mkdirp": { "version": "0.5.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "requires": { "minimist": "0.0.8" } }, "ms": { "version": "2.0.0", - "bundled": true + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, "needle": { "version": "2.2.3", - "bundled": true, + "resolved": "https://registry.npmjs.org/needle/-/needle-2.2.3.tgz", + "integrity": "sha512-GPL22d/U9cai87FcCPO6e+MT3vyHS2j+zwotakDc7kE2DtUAqFKMXLJCTtRp+5S75vXIwQPvIxkvlctxf9q4gQ==", "requires": { "debug": "2.6.9", "iconv-lite": "0.4.24", @@ -474,7 +510,8 @@ }, "node-pre-gyp": { "version": "0.11.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.11.0.tgz", + "integrity": "sha512-TwWAOZb0j7e9eGaf9esRx3ZcLaE5tQ2lvYy1pb5IAaG1a2e2Kv5Lms1Y4hpj+ciXJRofIxxlt5haeQ/2ANeE0Q==", "requires": { "detect-libc": "1.0.3", "mkdirp": "0.5.1", @@ -490,7 +527,8 @@ }, "nopt": { "version": "4.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", + "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", "requires": { "abbrev": "1.1.1", "osenv": "0.1.5" @@ -498,11 +536,13 @@ }, "npm-bundled": { "version": "1.0.5", - "bundled": true + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.0.5.tgz", + "integrity": "sha512-m/e6jgWu8/v5niCUKQi9qQl8QdeEduFA96xHDDzFGqly0OOjI7c+60KM/2sppfnUU9JJagf+zs+yGhqSOFj71g==" }, "npm-packlist": { "version": "1.1.11", - "bundled": true, + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.1.11.tgz", + "integrity": "sha512-CxKlZ24urLkJk+9kCm48RTQ7L4hsmgSVzEk0TLGPzzyuFxD7VNgy5Sl24tOLMzQv773a/NeJ1ce1DKeacqffEA==", "requires": { "ignore-walk": "3.0.1", "npm-bundled": "1.0.5" @@ -510,7 +550,8 @@ }, "npmlog": { "version": "4.1.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", "requires": { "are-we-there-yet": "1.1.5", "console-control-strings": "1.1.0", @@ -520,30 +561,36 @@ }, "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=" }, "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=" }, "once": { "version": "1.4.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "requires": { "wrappy": "1.0.2" } }, "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=" }, "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=" }, "osenv": { "version": "0.1.5", - "bundled": true, + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", "requires": { "os-homedir": "1.0.2", "os-tmpdir": "1.0.2" @@ -551,15 +598,18 @@ }, "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=" }, "process-nextick-args": { "version": "2.0.0", - "bundled": true + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" }, "rc": { "version": "1.2.8", - "bundled": true, + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", "requires": { "deep-extend": "0.6.0", "ini": "1.3.5", @@ -569,13 +619,15 @@ "dependencies": { "minimist": { "version": "1.2.0", - "bundled": true + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" } } }, "readable-stream": { "version": "2.3.5", - "bundled": true, + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.5.tgz", + "integrity": "sha512-tK0yDhrkygt/knjowCUiWP9YdV7c5R+8cR0r/kt9ZhBU906Fs6RpQJCEilamRJj1Nx2rWI6LkW9gKqjTkshhEw==", "requires": { "core-util-is": "1.0.2", "inherits": "2.0.3", @@ -588,38 +640,46 @@ }, "rimraf": { "version": "2.6.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", + "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", "requires": { "glob": "7.1.2" } }, "safe-buffer": { "version": "5.1.1", - "bundled": true + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" }, "safer-buffer": { "version": "2.1.2", - "bundled": true + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "sax": { "version": "1.2.4", - "bundled": true + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" }, "semver": { "version": "5.5.1", - "bundled": true + "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.1.tgz", + "integrity": "sha512-PqpAxfrEhlSUWge8dwIp4tZnQ25DIOthpiaHNIthsjEFQD6EvqUKUDM7L8O2rShkFccYo1VjJR0coWfNkCubRw==" }, "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=" }, "signal-exit": { "version": "3.0.2", - "bundled": true + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" }, "string-width": { "version": "1.0.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "requires": { "code-point-at": "1.1.0", "is-fullwidth-code-point": "1.0.0", @@ -628,25 +688,29 @@ }, "string_decoder": { "version": "1.0.3", - "bundled": true, + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", "requires": { "safe-buffer": "5.1.1" } }, "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=", "requires": { "ansi-regex": "2.1.1" } }, "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=" }, "tar": { "version": "4.4.6", - "bundled": true, + "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.6.tgz", + "integrity": "sha512-tMkTnh9EdzxyfW+6GK6fCahagXsnYk6kE6S9Gr9pjVdys769+laCTbodXDhPAjzVtEBazRgP0gYqOjnk9dQzLg==", "requires": { "chownr": "1.1.1", "fs-minipass": "1.2.5", @@ -659,28 +723,33 @@ "dependencies": { "safe-buffer": { "version": "5.1.2", - "bundled": true + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "yallist": { "version": "3.0.2", - "bundled": true + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.2.tgz", + "integrity": "sha1-hFK0u36Dx8GI2AQcGoN8dz1ti7k=" } } }, "util-deprecate": { "version": "1.0.2", - "bundled": true + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, "wide-align": { "version": "1.1.3", - "bundled": true, + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", "requires": { "string-width": "1.0.2" } }, "wrappy": { "version": "1.0.2", - "bundled": true + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" } } }, @@ -1357,6 +1426,15 @@ "utils-merge": "1.0.1" } }, + "express-validator": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/express-validator/-/express-validator-5.3.0.tgz", + "integrity": "sha512-HYVtPt21zp2bHS4+xwxYNF63dlq/23kh+ZRVfyo7SBObhOpRyZ0vWolm/v9KPUfCyLqX8j7ZP42dbB0MWjCCcA==", + "requires": { + "lodash": "4.17.11", + "validator": "10.9.0" + } + }, "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -3840,6 +3918,11 @@ "homedir-polyfill": "1.0.1" } }, + "validator": { + "version": "10.9.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-10.9.0.tgz", + "integrity": "sha512-hZJcZSWz9poXBlAkjjcsNAdrZ6JbjD3kWlNjq/+vE7RLLS/+8PAj3qVVwrwsOz/WL8jPmZ1hYkRvtlUeZAm4ug==" + }, "vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", diff --git a/package.json b/package.json index a621b1f..02f5f7f 100755 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "dotenv": "^5.0.1", "express": "^4.16.3", "express-session": "^1.15.6", + "express-validator": "^5.3.0", "googleapis": "^27.0.0", "jasmine": "^3.1.0", "jsonwebtoken": "^8.2.1", diff --git a/server/controllers/main.js b/server/controllers/main.js index aa18912..08d060e 100755 --- a/server/controllers/main.js +++ b/server/controllers/main.js @@ -7,6 +7,7 @@ const common = require('../domain/common/index'); const reviews = require('../domain/reviews/index'); const hashtags = require('../domain/hashtags/index'); const utils = require('../utils/helpers'); +const validators = require('../validators/main'); router.get('/playlists', utils.authOptional, (req, res) => { const uuid = req.user ? req.user.id : null; @@ -25,14 +26,14 @@ router.get('/playlist/:playlist_id', utils.authOptional, async (req, res) => { }).catch(err => res.json(err)) }); -router.post('/playlist', utils.auth, async (req, res) => { +router.post('/playlist', utils.auth, validators.createPlaylist,async (req, res) => { const uuid = req.user.id; playlist.createPlaylist(uuid, req.body).then(async (id) => { res.json(await playlist.getPlaylist(id)); }).catch(err => res.json(err)) }); -router.put('/playlist', utils.auth, (req, res) => { +router.put('/playlist', utils.auth, validators.updatePlaylist, (req, res) => { const uuid = req.user.id; playlist.updatePlaylist(uuid, req.body).then(data => { res.json(data) @@ -52,26 +53,26 @@ router.post('/playlist-reorder/:playlist_id', utils.auth, async (req, res) => { res.json({success: true}); }).catch(err => res.json(err)) }); -router.post('/playlist-import', utils.auth, (req, res) => { + +router.post('/playlist-import', utils.auth, validators.playlistImport, (req, res) => { const uuid = req.user.id; const youtubePlaylistId = utils.getParameterByName('list', req.body.yt_url); - if (!youtubePlaylistId) res.json({success: false, reason: "Youtube playlist url not valid"}); - else { - youtube.importPlaylistFromYoutube(uuid, req.body, youtubePlaylistId).then(() => { - res.json({success: true}); - }).catch(err => res.json(err)) - } + const playlist = req.body; + playlist.youtube_playlist_id = youtubePlaylistId; + youtube.importPlaylistFromYoutube(uuid, playlist).then(() => { + res.json({success: true}); + }).catch(err => res.json(err)) + }); -router.post('/add-video', utils.auth, (req, res) => { + +router.post('/add-video', utils.auth, validators.addVideo, (req, res) => { const uuid = req.user.id; - if (!req.body.playlist_id) res.json({success: false, reason: "playlist_id is mandatory"}); - else { - video.addVideoToPlaylist(uuid, req.body).then((data) => { - res.json(data) - }).catch(err => res.json(err)) - } + video.addVideoToPlaylist(uuid, req.body).then((data) => { + res.json(data) + }).catch(err => res.json(err)) + }); -router.post('/remove-video', utils.auth, (req, res) => { +router.post('/remove-video', utils.auth, validators.removeVideo, (req, res) => { const uuid = req.user.id; video.deleteVideo(uuid, req.body.playlist_id, req.body.video_id).then(data => { res.json(data) @@ -96,7 +97,7 @@ router.get('/categories', (req, res) => { }).catch(err => res.json(err)); }); -router.put('/video', utils.auth, (req, res) => { +router.put('/video', utils.auth, validators.updateVideo, (req, res) => { const uuid = req.user.id; video.updateVideo(uuid, req.body).then(data => { res.json({success: true}) @@ -104,7 +105,7 @@ router.put('/video', utils.auth, (req, res) => { }); //Route used to get signed s3 url -router.post('/upload-file', (req, res) => { +router.post('/upload-file', validators.uploadFile, (req, res) => { thumbnails.getSignedUrl(req.body).then(data => { res.json({url: data}) }).catch(err => res.json(err)) @@ -116,13 +117,13 @@ router.get('/suggestions', (req, res) => { }).catch(err => res.json(err)) }); -router.post('/suggestion', (req, res) => { +router.post('/suggestion', validators.saveSuggestion, (req, res) => { common.saveSuggestion(req.body).then(data => { res.json({success: true}); }).catch(err => res.json(err)) }); -router.put('/suggestion', (req, res) => { +router.put('/suggestion', validators.updateSuggestion, (req, res) => { common.updateSuggestion(req.body).then(data => { res.json({success: true}); }).catch(err => res.json(err)) @@ -140,7 +141,7 @@ router.get('/reviews/:playlist_id', (req, res) => { }).catch(err => res.json(err)) }); //router.use(utils.auth); -router.post('/review', utils.auth, (req, res) => { +router.post('/review', [utils.auth, validators.createReview], (req, res) => { const uuid = req.user.id; reviews.createReview(uuid, req.body).then(data => { res.json({success: true}); diff --git a/server/controllers/user.js b/server/controllers/user.js index f875cf4..9c43944 100644 --- a/server/controllers/user.js +++ b/server/controllers/user.js @@ -3,16 +3,16 @@ const users = require('../domain/user/index'); const helpers = require('../utils/helpers'); const bookmarks = require('../domain/bookmarks/index'); const passport = require('passport'); -const jwt = require('jsonwebtoken'); +const userValidators = require('../validators/user'); -router.post('/register', (req, res) => { +router.post('/register', userValidators.registerUser, (req, res) => { users.registerUser(req.body).then(data => { res.json(data); }).catch(err => res.json(err)) }); -router.post('/login', (req, res) => { +router.post('/login', userValidators.loginUser, (req, res) => { users.loginUser(req.body.email, req.body.password).then(data => { res.json(data); }).catch(err => res.json(err)) @@ -23,42 +23,26 @@ router.get('/auth/:platform', (req, res, next) => passport.authenticate(req.para res.json(user); })(req, res, next)); -router.post('/reset-password-request', (req, res) => { +router.post('/reset-password-request', userValidators.resetPasswordRequest, (req, res) => { const email = req.body.email; email ? users.resetPasswordRequest(email).then(data => { res.json(data); }) : res.json({success: false, reason: "Email address is required."}) }); -router.post('/reset-password', (req, res) => { - if (!req.body.password_reset_token) { - res.json({success: false, reason: "Missing token."}) - } else if (!req.body.password) { - res.json({success: false, reason: "Missing password."}) - } else { +router.post('/reset-password', userValidators.resetPassword, (req, res) => { users.resetPasswordProcess(req.body.password_reset_token, req.body.password).then(data => { res.json(data) }).catch(err => res.json(err)) - } -}); - -router.post('/youtube-login', async (req, res) => { - users.registerOrLoginUserGoogle(req.body.code).then(data => { - data.token = jwt.sign(data.user, process.env.JWT_PASSWORD); - res.json(data); - }).catch(err => res.json(err.message)) }); -router.post('/confirm-email-request', (req, res) => { - if (!req.body.email) res.json({ success: false, reason: "Email is missing." }); - else { +router.post('/confirm-email-request', userValidators.confirmEmailRequest, (req, res) => { users.sendConfirmEmailLink(req.body.email).then(data => { res.json(data); }).catch(err => res.json(err)) - } }); -router.post('/confirm-email', (req, res) => { +router.post('/confirm-email', userValidators.confirmEmail, (req, res) => { users.confirmEmail(req.body.email_confirm_token).then(data => { res.json({success: true}); }).catch(err => res.json(err)) @@ -77,7 +61,7 @@ router.get('/info', (req, res) => { } }); -router.put('/info', (req, res) => { +router.put('/info', userValidators.updateUserInfo, (req, res) => { const user = req.body; user.id = req.user.id; if (user.id) { @@ -106,7 +90,7 @@ router.get('/bookmarks', (req, res) => { }).catch(err => res.json(err)); }); -router.post('/bookmark', (req, res) => { +router.post('/bookmark', userValidators.createBookmark, (req, res) => { const uuid = req.user.id; bookmarks.createBookmark(uuid, req.body.playlist_id).then(data => { res.json(data); @@ -120,7 +104,7 @@ router.delete('/bookmark/:playlist_id', (req, res) => { }).catch(err => res.json(err)); }); -router.put('/onboarding', (req, res) => { +router.put('/onboarding', userValidators.onboarding, (req, res) => { const uuid = req.user.id; users.updateOnboarding(uuid, req.body).then(data => { res.json({success: true}); diff --git a/server/domain/playlist/index.js b/server/domain/playlist/index.js index 6ca0164..29eaffa 100644 --- a/server/domain/playlist/index.js +++ b/server/domain/playlist/index.js @@ -193,8 +193,9 @@ function createPlaylist(user_id, playlist) { title: playlist.title, url: playlist.url, description: playlist.description, - category_id: playlist.category_id, + category_id: playlist.category.id, hashtags: playlist.hashtags, + classification: playlist.classification, status: playlist.status || 'hidden', playlist_thumbnail_url: playlist.playlist_thumbnail_url, youtube_playlist_id: playlist.youtube_playlist_id, diff --git a/server/domain/user/index.js b/server/domain/user/index.js index be1583b..ccf3f82 100644 --- a/server/domain/user/index.js +++ b/server/domain/user/index.js @@ -5,24 +5,6 @@ const jwt = require('jsonwebtoken'); const youtube = require('../youtube/index'); const emails = require('../email/index'); const crm = require('../crm/index'); -//const facebook = require('../login-adapters/facebook/index'); -//console.log(facebook.initializePassportStrategy, "??????") - -async function registerOrLoginUserGoogle(code){ //For Google - const googleUser = await youtube.getUserInfoByCode(code); - - let data = await registerUser({ - email: googleUser.email, - first_name: googleUser.given_name, - last_name: googleUser.family_name, - g_access_token: googleUser.g_access_token, - g_refresh_token: googleUser.g_refresh_token, - avatar_url: googleUser.picture - }); - data.user = getCleanUserAndJwt(data.user); - return data; - -} async function registerUser(user){ const existing = await db.select('*').from('user').where('email', user.email).reduce(helpers.getFirst); @@ -53,6 +35,7 @@ async function afterRegisterProcess(newUser) { async function loginUser(email, password) { const user = await db.select('*').from('user').where('email', email).reduce(helpers.getFirst); if (user) { + if (!user.password_hash) return {success: false, reason: "You do not have a password set up. Reset your password to continue."}; const isPasswordValid = await helpers.compareBcryptHash(password, user.password_hash); const data = getCleanUserAndJwt(user); return !isPasswordValid @@ -117,28 +100,19 @@ async function sendConfirmEmailLink(email) { } } -function updateUserDetails(user) { - return db.from('user').update({ - first_name: user.first_name, - last_name: user.last_name, - g_access_token: user.g_access_token, - g_refresh_token: user.g_refresh_token, - avatar_url: user.avatar_url - }).where('id', user.id) -} - async function updateUserBasicInfo(user) { if (!user.alias) { return { success: false, message: "Alias cannot be empty." } } const exists = await db.from("user").select('id').where('alias', user.alias).reduce(helpers.getFirst); - if (exists) { + if (exists && exists.id !== user.id) { return { success: false, message: "Alias already taken." } } return db.from('user').update({ first_name: user.first_name, last_name: user.last_name, - avatar_url: user.avatar_url + avatar_url: user.avatar_url, + alias: user.alias }).where('id', user.id).then(async () => Promise.resolve({ success: true, user: await getUserById(user.id) })) } @@ -212,4 +186,4 @@ async function updateOnboarding(user_id, onboarding){ async function getOnboarding(user_id){ return db.select('*').from('onboarding').where('user_id', user_id).reduce(helpers.getFirst); } -module.exports = { afterRegisterProcess, getUserByEmail, getCleanUserAndJwt, updateOnboarding, getOnboarding, registerUser, loginUser, resetPasswordRequest, resetPasswordProcess, registerOrLoginUserGoogle, updateUserPassword, sendConfirmEmailLink, confirmEmail, getUserById, updateUserBasicInfo, changeUserPassword }; +module.exports = { afterRegisterProcess, getUserByEmail, getCleanUserAndJwt, updateOnboarding, getOnboarding, registerUser, loginUser, resetPasswordRequest, resetPasswordProcess, updateUserPassword, sendConfirmEmailLink, confirmEmail, getUserById, updateUserBasicInfo, changeUserPassword }; diff --git a/server/domain/youtube/index.js b/server/domain/youtube/index.js index e0dd6cd..fa71737 100755 --- a/server/domain/youtube/index.js +++ b/server/domain/youtube/index.js @@ -162,17 +162,10 @@ function fetchYoutubePlaylistById(playlist_id){ }) } -async function importPlaylistFromYoutube(user_id, playlistMetadata, youtubePlaylistId){ - const youtubePlaylist = await fetchYoutubePlaylistById(youtubePlaylistId); +async function importPlaylistFromYoutube(user_id, playlistMetadata){ + const youtubePlaylist = await fetchYoutubePlaylistById(playlistMetadata.youtube_playlist_id); const videos = youtubePlaylist.data.items; - const playlist_id = await playlist.createPlaylist(user_id, { - title: playlistMetadata.title, - description: playlistMetadata.description, - category: playlistMetadata.category, - status: playlistMetadata.status || 'hidden', - playlist_thumbnail_url: playlistMetadata.playlist_thumbnail_url, - youtube_playlist_id: youtubePlaylistId - }); + const playlist_id = await playlist.createPlaylist(user_id, playlistMetadata); await Promise.all(videos.map(async(i, index) => { let videoItem = await getVideoMetadata(i.contentDetails.videoId); if (videoItem) { diff --git a/server/resources/playlist-website/index.html b/server/resources/playlist-website/index.html index 53d8594..8947d0f 100644 --- a/server/resources/playlist-website/index.html +++ b/server/resources/playlist-website/index.html @@ -63,11 +63,11 @@ $scope.search = {}; $scope.filterResults = {}; let user_id = $location.search().id || localStorage.getItem('uuid') || guid(); //'Viewly'; - + console.log(user_id, "Active"); if ($location.search().vidflowadminsomethingxcv) { $scope.admin = true; - user_id = $location.search().id || 'Viewly'; + user_id = $location.search().id; } localStorage.setItem('uuid', user_id); diff --git a/server/validators/index.js b/server/validators/index.js new file mode 100644 index 0000000..0eb1747 --- /dev/null +++ b/server/validators/index.js @@ -0,0 +1,22 @@ +const { validationResult } = require('express-validator/check'); +const { matchedData } = require('express-validator/filter'); +const { check } = require('express-validator/check'); + +const generalValidators = { + email: check('email').isEmail().trim().withMessage('Email must be a valid email address.'), + password: check('password').isLength({ min: 8 }).withMessage('Password must be contain at least 8 characters.'), +}; + + +function validateErrors(req, res, next) { + const errors = validationResult(req); + if (!errors.isEmpty()) { + return res.status(422).json({ errors: errors.array() }); + } else next(); + +} +function matchData(req, res, next){ + req.body = matchedData(req, { locations: ['body'] }); + next(); +} +module.exports = { generalValidators, validateErrors, matchData }; diff --git a/server/validators/main.js b/server/validators/main.js new file mode 100644 index 0000000..e4ea225 --- /dev/null +++ b/server/validators/main.js @@ -0,0 +1,82 @@ +const { validationResult } = require('express-validator/check'); +const { matchedData } = require('express-validator/filter'); +const { check, body } = require('express-validator/check'); +const { validateErrors, matchData } = require('./index'); + +const createPlaylist = [ + //Mandatory below + check('title').trim().isLength({ min: 4 }).isString().withMessage('Title is mandatory and must be at least 4 characters.'), + check('category.id').isInt().withMessage('Category is required. Ex (3)'), + //Optional below + check(['url', 'hashtags']).trim().optional().isLength({ min: 4 }).isString(), + check('description').trim().optional().isLength({ min: 10 }).withMessage('Description must be at least 10 characters.'), + check('status').optional().isString(), + check('classification').optional().isString(), + check('playlist_thumbnail_url').isString().withMessage('Must be a valid url').optional({nullable: true}), + check('youtube_playlist_id').isString().optional({nullable: true}), + check('publish_date').optional() +]; + +const updatePlaylist = [ + check('id').isUUID().withMessage('id is required.'), + ...createPlaylist +]; + +const reorderPlaylist = [ + body().isArray() +]; + +const playlistImport = [ + ...createPlaylist, + check('yt_url').isURL().withMessage('Youtube playlist url not valid') +]; + +const addVideo = [ + check('video_id').isString(), + check('playlist_id').isUUID().withMessage('playlist_id is required.'), + check('title').isString().trim().isLength({ min: 8 }).withMessage('Title must be contain at least 8 characters.'), + check('description').isString().trim().optional({ nullable: true }), + check('thumbnail_url').isURL().trim().optional() +]; + +const removeVideo = [ + check('playlist_id').isUUID().withMessage('playlist_id (uuid) is required.'), + check('video_id').isInt().withMessage('video_id (int) is required.'), +]; + +const updateVideo = [ + check('id').isInt().withMessage('id is required.'), + check('title').isString().trim().optional().isLength({ min: 4 }).withMessage('Title must be at least 4 characters.'), + check('description').isString().trim().optional({ nullable: true }).isLength({ min: 20 }).withMessage('Description must be at least 20 characters.'), +]; + +const uploadFile = [ + check('key').isString().trim().withMessage('key is a required attribute.'), + check('type').isString().trim().withMessage('type is a required attribute.'), +]; + +const saveSuggestion = [ + check('type').isString().trim().withMessage('key is a required attribute.').optional(), + check('title').isString().trim().withMessage('key is a required attribute.').optional(), + check('description').trim().isString().withMessage('key is a required attribute.').optional(), + check('url').trim().isURL().withMessage('key is a required attribute.').optional(), + check('email').isEmail().trim().withMessage('key is a required attribute.').optional(), + check('playlist_id').isUUID().optional(), + check('category').optional() +]; + +const updateSuggestion = [ + check('id').isUUID(), + ...saveSuggestion +]; + +const createReview = [ + check('playlist_id').isUUID().withMessage('playlist_id is a required attribute.').optional(), + check(['title', 'description']).isString().isLength({ min: 5 }), + check('rating').isInt() +]; + +const validators = { createPlaylist, updatePlaylist, reorderPlaylist, playlistImport, addVideo, removeVideo, updateVideo, uploadFile, saveSuggestion, updateSuggestion, createReview }; +Object.keys(validators).forEach(key => { validators[key].push(validateErrors, matchData) }); +module.exports = validators; + diff --git a/server/validators/user.js b/server/validators/user.js new file mode 100644 index 0000000..1740e38 --- /dev/null +++ b/server/validators/user.js @@ -0,0 +1,56 @@ +const { check } = require('express-validator/check'); +const { validateErrors, matchData, generalValidators } = require('./index'); + +const registerUser = [ + generalValidators.email, + generalValidators.password, + check(['first_name', 'last_name']).trim().optional(), + check('avatar_url').optional().trim().isURL().withMessage('Must be a valid url.') +]; + +const loginUser = [ + generalValidators.email, + generalValidators.password, +]; + +const youtubeLogin = [ + check('code').trim().withMessage('Code is required.') +]; + +const resetPasswordRequest = [ + generalValidators.email +]; + +const resetPassword = [ + generalValidators.password, + check('password_reset_token').isUUID().withMessage('password_reset_token is required.') +]; + +const confirmEmailRequest = [ + generalValidators.email +]; + +const confirmEmail = [ + check('email').isEmail().trim().withMessage('Email must be a valid email address.'), + check('email_confirm_token').isUUID() +]; + +const createBookmark = [ + check('playlist_id').isUUID() +]; + +const onboarding = [ + check('categories_ids').isArray(), + check('time_to_spend').isInt().optional(), + check('step').isInt().optional() +]; + +const updateUserInfo = [ + check('alias').trim().isLength({ min: 4 }).isString().withMessage('Alias cannot be empty. Must be 4 characters minimum'), + check(['first_name', 'last_name', 'avatar_url']).trim().optional().isLength({ min: 3 }).isString(), +]; + +const validators = {registerUser, loginUser, youtubeLogin, resetPasswordRequest, resetPassword, confirmEmailRequest, confirmEmail, createBookmark, onboarding, updateUserInfo}; +Object.keys(validators).forEach(key => { validators[key].push(validateErrors, matchData) }); + +module.exports = validators;