diff --git a/.env.sample b/.env.sample index 61af3ee..84d1a16 100644 --- a/.env.sample +++ b/.env.sample @@ -22,3 +22,4 @@ TWITTER_SECRET='ggug' CLOUDINARY_API_KEY=xxxxxxxxxxxxxxxxx CLOUDINARY_SECRET_KEY=xxxxxxxxxxxxxxxx CLOUDINARY_NAME=xxxxxxxxxxxxx +TWITTER_CALLBACK_URL='ghgfh' diff --git a/.gitignore b/.gitignore index e6e29fa..48588af 100644 --- a/.gitignore +++ b/.gitignore @@ -94,6 +94,8 @@ typings/ #ignore vscode settings file .vscode/settings.json +#ignore DS_Store +.DS_Store # End of https://www.gitignore.io/api/node diff --git a/package-lock.json b/package-lock.json index 55423e3..7516d81 100644 --- a/package-lock.json +++ b/package-lock.json @@ -613,9 +613,9 @@ }, "dependencies": { "semver": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", - "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==" + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" } } }, @@ -808,11 +808,6 @@ "to-fast-properties": "^2.0.0" } }, - "@bcoe/v8-coverage": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.1.tgz", - "integrity": "sha512-KioOCsSvSvXx6xUNLiJz+P+VMb7NRcePjoefOr74Y5P6lEKsiOn35eZyZzgpK4XCNJdXTDR7+zykj0lwxRvZ2g==" - }, "@sendgrid/client": { "version": "6.4.0", "resolved": "https://registry.npmjs.org/@sendgrid/client/-/client-6.4.0.tgz", @@ -858,16 +853,6 @@ "integrity": "sha512-aRnpPa7ysx3aNW60hTiCtLHlQaIFsXFCgQlpakNgDNVFzbtusSY8PwjAQgRWfSk0ekNoBjO51eQRB6upA9uuyw==", "dev": true }, - "@types/is-windows": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@types/is-windows/-/is-windows-0.2.0.tgz", - "integrity": "sha1-byTuSHMdMRaOpRBhDW3RXl/Jxv8=" - }, - "@types/istanbul-lib-coverage": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz", - "integrity": "sha512-hRJD2ahnnpLgsj6KWMYSrmXkM3rm2Dl1qkx6IOFD5FnuNPXJIG5L0dhgKXCYTRMGzU4n0wImQ/xfmRc4POUFlg==" - }, "@types/node": { "version": "12.6.8", "resolved": "https://registry.npmjs.org/@types/node/-/node-12.6.8.tgz", @@ -911,11 +896,6 @@ "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-2.3.5.tgz", "integrity": "sha512-SCcK7mvGi3+ZNz833RRjFIxrn4gI1PPR3NtuIS+6vMkvmsGjosqTJwRt5bAEFLRz+wtJMWv8+uOnZf2hi2QXTg==" }, - "abab": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.0.tgz", - "integrity": "sha512-sY5AXXVZv4Y1VACTtR11UJCPHHudgY5i26Qj5TypE6DKlIApbwb5uqhXcJ5UUGbvZNRh7EeIoW+LrJumBsKp7w==" - }, "abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -933,16 +913,8 @@ "acorn": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.2.1.tgz", - "integrity": "sha512-JD0xT5FCRDNyjDda3Lrg/IxFscp9q4tiYtxE1/nOzlKCk7hIRuYjhq1kCNkbPjMRMZuFq20HNQn1I9k8Oj0E+Q==" - }, - "acorn-globals": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-4.3.3.tgz", - "integrity": "sha512-vkR40VwS2SYO98AIeFvzWWh+xyc2qi9s7OoXSFEGIP/rOJKzjnhykaZJNnHdoq4BL2gGxI5EZOU16z896EYnOQ==", - "requires": { - "acorn": "^6.0.1", - "acorn-walk": "^6.0.1" - } + "integrity": "sha512-JD0xT5FCRDNyjDda3Lrg/IxFscp9q4tiYtxE1/nOzlKCk7hIRuYjhq1kCNkbPjMRMZuFq20HNQn1I9k8Oj0E+Q==", + "dev": true }, "acorn-jsx": { "version": "5.0.1", @@ -950,11 +922,6 @@ "integrity": "sha512-HJ7CfNHrfJLlNTzIEUTj43LNWGkqpRLxm3YjAlcD0ACydk9XynzYsCBHxut+iqt+1aBXkx9UP/w/ZqMr13XIzg==", "dev": true }, - "acorn-walk": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.2.0.tgz", - "integrity": "sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA==" - }, "ajv": { "version": "6.10.2", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", @@ -1103,11 +1070,6 @@ "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=" }, - "array-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz", - "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=" - }, "array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", @@ -1158,21 +1120,11 @@ "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", "dev": true }, - "async": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=" - }, "async-each": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==" }, - "async-limiter": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", - "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==" - }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -1402,11 +1354,6 @@ } } }, - "browser-process-hrtime": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-0.1.3.tgz", - "integrity": "sha512-bRFnI4NnjO6cnyLmOV/7PVoDEMJChlcfN0z4s1YMBY989/SvlfMI1lgCnkFUs53e9gQF+w7qu7XdllSTiSl8Aw==" - }, "browser-stdout": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", @@ -1475,78 +1422,6 @@ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" }, - "c8": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/c8/-/c8-5.0.1.tgz", - "integrity": "sha512-63V/XrzqX2wgByuJyafJYuaudvxxmGrm/acqRJpvbtKvRUj6h/85EFrVweQ14j1F7wRASp0W9zyZqj8fnWI+4Q==", - "requires": { - "@bcoe/v8-coverage": "^0.2.1", - "find-up": "^3.0.0", - "foreground-child": "^1.5.6", - "furi": "^1.3.0", - "istanbul-lib-coverage": "^2.0.1", - "istanbul-lib-report": "^2.0.1", - "istanbul-reports": "^2.2.4", - "rimraf": "^2.6.2", - "test-exclude": "^5.0.0", - "v8-to-istanbul": "^3.1.3", - "yargs": "^13.1.0", - "yargs-parser": "^10.1.0" - }, - "dependencies": { - "camelcase": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=" - }, - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "requires": { - "locate-path": "^3.0.0" - } - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "p-limit": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", - "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "requires": { - "p-limit": "^2.0.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" - }, - "yargs-parser": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-10.1.0.tgz", - "integrity": "sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==", - "requires": { - "camelcase": "^4.1.0" - } - } - } - }, "cache-base": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", @@ -1981,6 +1856,25 @@ "integrity": "sha512-cgHI1azax5ATrZ8rJ+ODDML9Fvu67PimB6aNxBrc/QwSaDaM9eTfIEUHx3bBLJJ82ioSb+/5zfsMCCEJax3ByQ==", "dev": true }, + "connect-multiparty": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/connect-multiparty/-/connect-multiparty-2.2.0.tgz", + "integrity": "sha512-zKcpA7cuXGEhuw9Pz7JmVCFmp85jzGLGm/iiagXTwyEAJp4ypLPtRS/V4IGuGb9KjjrgHBs6P/gDCpZHnFzksA==", + "requires": { + "http-errors": "~1.7.0", + "multiparty": "~4.2.1", + "on-finished": "~2.3.0", + "qs": "~6.5.2", + "type-is": "~1.6.16" + }, + "dependencies": { + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + } + } + }, "console-control-strings": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", @@ -2068,36 +1962,6 @@ "vary": "^1" } }, - "coverage": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/coverage/-/coverage-0.4.1.tgz", - "integrity": "sha512-Nwe6RSpwaUR6R++b5QukGrbu3rpeSOGZ805f6IXwG63pIaJZ7NV5osfDgJ43Fz0B9IwXha+jwArWB8Tpngi8lA==", - "requires": { - "c8": "^5.0.1", - "foreground-child": "^1.5.6", - "normalize-package-data": "^2.5.0", - "slash": "^3.0.0", - "test-exclude": "^5.2.3", - "which": "^1.3.1", - "yargs-parser": "^13.1.1" - }, - "dependencies": { - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==" - }, - "yargs-parser": { - "version": "13.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.1.tgz", - "integrity": "sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==", - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } - } - } - }, "coveralls": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/coveralls/-/coveralls-3.0.5.tgz", @@ -2173,19 +2037,6 @@ "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=", "dev": true }, - "cssom": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", - "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==" - }, - "cssstyle": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-1.4.0.tgz", - "integrity": "sha512-GBrLZYZ4X4x6/QEoBnIrqb8B/f5l4+8me2dkom/j1Gtbxy0kBv6OGzKuAsGM75bkGwGAFkt56Iwg28S3XTZgSA==", - "requires": { - "cssom": "0.3.x" - } - }, "d": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", @@ -2203,28 +2054,6 @@ "assert-plus": "^1.0.0" } }, - "data-urls": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-1.1.0.tgz", - "integrity": "sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ==", - "requires": { - "abab": "^2.0.0", - "whatwg-mimetype": "^2.2.0", - "whatwg-url": "^7.0.0" - }, - "dependencies": { - "whatwg-url": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.0.0.tgz", - "integrity": "sha512-37GeVSIJ3kn1JgKyjiYNmSLP1yzbpb29jdmwBSgkD9h40/hyrR/OifpVUndji3tmwGgD8qpw7iQu3RSbCrBpsQ==", - "requires": { - "lodash.sortby": "^4.7.0", - "tr46": "^1.0.1", - "webidl-conversions": "^4.0.2" - } - } - } - }, "debug": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", @@ -2260,7 +2089,8 @@ "deep-is": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=" + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true }, "deepmerge": { "version": "2.2.1", @@ -2401,14 +2231,6 @@ "esutils": "^2.0.2" } }, - "domexception": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz", - "integrity": "sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug==", - "requires": { - "webidl-conversions": "^4.0.2" - } - }, "dot-prop": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz", @@ -2474,11 +2296,6 @@ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, - "ejs": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.6.2.tgz", - "integrity": "sha512-PcW2a0tyTuPHz3tWyYqtK6r1fZ3gp+3Sop8Ph+ZYN81Ob5rwmbHEzaqs10N3BEsaGTkh/ooniXK+WwszGlc2+Q==" - }, "electron-to-chromium": { "version": "1.3.207", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.207.tgz", @@ -2523,15 +2340,6 @@ "is-arrayish": "^0.2.1" } }, - "errorhandler": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/errorhandler/-/errorhandler-1.5.1.tgz", - "integrity": "sha512-rcOwbfvP1WTViVoUjcfZicVzjhjTuhSMntHh6mW3IrEiyE6mJyXvsToJUJGlGlw/2xU9P5whlWNGlIDVeCiT4A==", - "requires": { - "accepts": "~1.3.7", - "escape-html": "~1.0.3" - } - }, "es-abstract": { "version": "1.13.0", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.13.0.tgz", @@ -2610,31 +2418,6 @@ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, - "escodegen": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.12.0.tgz", - "integrity": "sha512-TuA+EhsanGcme5T3R0L80u4t8CpbXQjegRmf7+FPTJrtCTErXFeelblRgHQa1FofEzqYYJmJ/OqjTwREp9qgmg==", - "requires": { - "esprima": "^3.1.3", - "estraverse": "^4.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1", - "source-map": "~0.6.1" - }, - "dependencies": { - "esprima": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", - "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=" - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "optional": true - } - } - }, "eslint": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.1.0.tgz", @@ -2853,7 +2636,8 @@ "estraverse": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", - "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=" + "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", + "dev": true }, "esutils": { "version": "2.0.2", @@ -2874,14 +2658,6 @@ "es5-ext": "~0.10.14" } }, - "eventsource": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.0.7.tgz", - "integrity": "sha512-4Ln17+vVT0k8aWq+t/bF5arcS3EpT9gYtW66EPacdj/mAFevznsnyoHLPy2BA8gbIQeIHoPsvwmfBftfcG//BQ==", - "requires": { - "original": "^1.0.0" - } - }, "execa": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", @@ -2998,17 +2774,6 @@ "resolved": "https://registry.npmjs.org/express-async-errors/-/express-async-errors-3.1.1.tgz", "integrity": "sha512-h6aK1da4tpqWSbyCa3FxB/V6Ehd4EEB15zyQq9qe75OZBp0krinNKuH4rAY+S/U/2I36vdLAUFSjQJ+TFmODng==" }, - "express-jwt": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/express-jwt/-/express-jwt-5.3.1.tgz", - "integrity": "sha512-1C9RNq0wMp/JvsH/qZMlg3SIPvKu14YkZ4YYv7gJQ1Vq+Dv8LH9tLKenS5vMNth45gTlEUGx+ycp9IHIlaHP/g==", - "requires": { - "async": "^1.5.0", - "express-unless": "^0.3.0", - "jsonwebtoken": "^8.1.0", - "lodash.set": "^4.0.0" - } - }, "express-session": { "version": "1.16.2", "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.16.2.tgz", @@ -3049,11 +2814,6 @@ } } }, - "express-unless": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/express-unless/-/express-unless-0.3.1.tgz", - "integrity": "sha1-JVfBRudb65A+LSR/m1ugFFJpbiA=" - }, "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -3166,13 +2926,22 @@ "fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true }, "fast-safe-stringify": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.6.tgz", "integrity": "sha512-q8BZ89jjc+mz08rSxROs8VsrBBcn1SIw1kq9NjolL509tkABRk9io01RAjSaEv1Xb2uFLt8VtRiZbGp5H8iDtg==" }, + "fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", + "requires": { + "pend": "~1.2.0" + } + }, "fecha": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fecha/-/fecha-2.3.3.tgz", @@ -3997,15 +3766,6 @@ "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", "dev": true }, - "furi": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/furi/-/furi-1.3.0.tgz", - "integrity": "sha512-TYoXEeRLKHXNWcCBP0VH1psPktQ9G8Y0GfZwMXCvwVbhbfNx7JItKWhB5mMBYufNjqxEHq+Ivd1nLtr5vQyVoQ==", - "requires": { - "@types/is-windows": "^0.2.0", - "is-windows": "^1.0.2" - } - }, "gauge": { "version": "2.7.4", "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", @@ -4276,14 +4036,6 @@ "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==" }, - "html-encoding-sniffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz", - "integrity": "sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw==", - "requires": { - "whatwg-encoding": "^1.0.1" - } - }, "http-errors": { "version": "1.7.2", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", @@ -4816,54 +4568,6 @@ "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" }, - "jsdom": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-11.12.0.tgz", - "integrity": "sha512-y8Px43oyiBM13Zc1z780FrfNLJCXTL40EWlty/LXUtcjykRBNgLlCjWXpfSPBl2iv+N7koQN+dvqszHZgT/Fjw==", - "requires": { - "abab": "^2.0.0", - "acorn": "^5.5.3", - "acorn-globals": "^4.1.0", - "array-equal": "^1.0.0", - "cssom": ">= 0.3.2 < 0.4.0", - "cssstyle": "^1.0.0", - "data-urls": "^1.0.0", - "domexception": "^1.0.1", - "escodegen": "^1.9.1", - "html-encoding-sniffer": "^1.0.2", - "left-pad": "^1.3.0", - "nwsapi": "^2.0.7", - "parse5": "4.0.0", - "pn": "^1.1.0", - "request": "^2.87.0", - "request-promise-native": "^1.0.5", - "sax": "^1.2.4", - "symbol-tree": "^3.2.2", - "tough-cookie": "^2.3.4", - "w3c-hr-time": "^1.0.1", - "webidl-conversions": "^4.0.2", - "whatwg-encoding": "^1.0.3", - "whatwg-mimetype": "^2.1.0", - "whatwg-url": "^6.4.1", - "ws": "^5.2.0", - "xml-name-validator": "^3.0.0" - }, - "dependencies": { - "acorn": { - "version": "5.7.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", - "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==" - }, - "ws": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-5.2.2.tgz", - "integrity": "sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA==", - "requires": { - "async-limiter": "~1.0.0" - } - } - } - }, "jsesc": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", @@ -5018,15 +4722,11 @@ "integrity": "sha1-GwuP+ayceIklBYK3C3ExXZ2m2aM=", "dev": true }, - "left-pad": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz", - "integrity": "sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==" - }, "levn": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, "requires": { "prelude-ls": "~1.1.2", "type-check": "~0.3.2" @@ -5109,16 +4809,6 @@ "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" }, - "lodash.set": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz", - "integrity": "sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM=" - }, - "lodash.sortby": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", - "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=" - }, "log-driver": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/log-driver/-/log-driver-1.2.7.tgz", @@ -5551,12 +5241,6 @@ } } }, - "mocha-lcov-reporter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/mocha-lcov-reporter/-/mocha-lcov-reporter-1.3.0.tgz", - "integrity": "sha1-Rpve9PivyaEWBW8HnfYYLQr7A4Q=", - "dev": true - }, "moment": { "version": "2.24.0", "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", @@ -5590,6 +5274,17 @@ "xtend": "^4.0.0" } }, + "multiparty": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/multiparty/-/multiparty-4.2.1.tgz", + "integrity": "sha512-AvESCnNoQlZiOfP9R4mxN8M9csy2L16EIbWIkt3l4FuGti9kXBS8QVzlfyg4HEnarJhrzZilgNFlZtqmoiAIIA==", + "requires": { + "fd-slicer": "1.1.0", + "http-errors": "~1.7.0", + "safe-buffer": "5.1.2", + "uid-safe": "2.1.5" + } + }, "mute-stream": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", @@ -5836,11 +5531,6 @@ "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" }, - "nwsapi": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.1.4.tgz", - "integrity": "sha512-iGfd9Y6SFdTNldEy2L0GUhcarIutFmk+MPWIn9dmj8NMIup03G08uUF2KGbbmv/Ux4RT0VZJoP/sVbWA6d/VIw==" - }, "nyc": { "version": "14.1.1", "resolved": "https://registry.npmjs.org/nyc/-/nyc-14.1.1.tgz", @@ -6091,6 +5781,7 @@ "version": "0.8.2", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", + "dev": true, "requires": { "deep-is": "~0.1.3", "fast-levenshtein": "~2.0.4", @@ -6100,14 +5791,6 @@ "wordwrap": "~1.0.0" } }, - "original": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/original/-/original-1.0.2.tgz", - "integrity": "sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg==", - "requires": { - "url-parse": "^1.4.3" - } - }, "os-homedir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", @@ -6245,11 +5928,6 @@ "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=" }, - "parse5": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz", - "integrity": "sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA==" - }, "parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -6302,14 +5980,6 @@ "passport-oauth2": "1.x.x" } }, - "passport-local": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/passport-local/-/passport-local-1.0.0.tgz", - "integrity": "sha1-H+YyaMkudWBmJkN+O5BmYsFbpu4=", - "requires": { - "passport-strategy": "1.x.x" - } - }, "passport-oauth1": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/passport-oauth1/-/passport-oauth1-1.1.0.tgz", @@ -6402,6 +6072,11 @@ "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", "integrity": "sha1-HUCLP9t2kjuVQ9lvtMnf1TXZy10=" }, + "pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=" + }, "performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", @@ -6494,11 +6169,6 @@ "find-up": "^2.1.0" } }, - "pn": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz", - "integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==" - }, "posix-character-classes": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", @@ -6530,7 +6200,8 @@ "prelude-ls": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=" + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true }, "prepend-http": { "version": "1.0.4", @@ -6608,11 +6279,6 @@ "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" }, - "querystringify": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.1.1.tgz", - "integrity": "sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==" - }, "random-bytes": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", @@ -6877,24 +6543,6 @@ } } }, - "request-promise-core": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.2.tgz", - "integrity": "sha512-UHYyq1MO8GsefGEt7EprS8UrXsm1TxEvFUX1IMTuSLU2Rh7fTIdFtl8xD7JiEYiWU2dl+NYAjCTksTehQUxPag==", - "requires": { - "lodash": "^4.17.11" - } - }, - "request-promise-native": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.7.tgz", - "integrity": "sha512-rIMnbBdgNViL37nZ1b3L/VfPOpSi0TqVDQPAvO6U14lMzOLrt5nilxCQqtDKhZeDiW0/hkCXGoQjhgJd/tCh6w==", - "requires": { - "request-promise-core": "1.1.2", - "stealthy-require": "^1.1.1", - "tough-cookie": "^2.3.3" - } - }, "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -6905,11 +6553,6 @@ "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" }, - "requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" - }, "resolve": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.11.1.tgz", @@ -7477,11 +7120,6 @@ "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" }, - "stealthy-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", - "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=" - }, "streamsearch": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", @@ -7650,11 +7288,6 @@ "swagger-ui-dist": "^3.18.1" } }, - "symbol-tree": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==" - }, "table": { "version": "5.4.4", "resolved": "https://registry.npmjs.org/table/-/table-5.4.4.tgz", @@ -7984,14 +7617,6 @@ } } }, - "tr46": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", - "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=", - "requires": { - "punycode": "^2.1.0" - } - }, "trim-right": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", @@ -8030,6 +7655,7 @@ "version": "0.3.2", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, "requires": { "prelude-ls": "~1.1.2" } @@ -8274,15 +7900,6 @@ "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=" }, - "url-parse": { - "version": "1.4.7", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.7.tgz", - "integrity": "sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg==", - "requires": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, "url-parse-lax": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", @@ -8318,23 +7935,6 @@ "integrity": "sha512-CNmdbwQMBjwr9Gsmohvm0pbL954tJrNzf6gWL3K+QMQf00PF7ERGrEiLgjuU3mKreLC2MeGhUsNV9ybTbLgd3w==", "dev": true }, - "v8-to-istanbul": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-3.2.3.tgz", - "integrity": "sha512-B8d/oxMtc/x0TYXr9b+Ywu5KexA/on4QMQ9M1kTYnoGZzKdo8LLk9ySlWePdDOtr2G0/2Injgcp3sOR9gU+3vQ==", - "requires": { - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^1.6.0", - "source-map": "^0.7.3" - }, - "dependencies": { - "source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==" - } - } - }, "v8flags": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.1.3.tgz", @@ -8372,42 +7972,6 @@ "extsprintf": "^1.2.0" } }, - "w3c-hr-time": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.1.tgz", - "integrity": "sha1-gqwr/2PZUOqeMYmlimViX+3xkEU=", - "requires": { - "browser-process-hrtime": "^0.1.2" - } - }, - "webidl-conversions": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", - "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==" - }, - "whatwg-encoding": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", - "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", - "requires": { - "iconv-lite": "0.4.24" - } - }, - "whatwg-mimetype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", - "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==" - }, - "whatwg-url": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.5.0.tgz", - "integrity": "sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ==", - "requires": { - "lodash.sortby": "^4.7.0", - "tr46": "^1.0.1", - "webidl-conversions": "^4.0.2" - } - }, "which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", @@ -8508,7 +8072,8 @@ "wordwrap": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=" + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "dev": true }, "wrap-ansi": { "version": "2.1.0", @@ -8576,25 +8141,12 @@ "signal-exit": "^3.0.2" } }, - "ws": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz", - "integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==", - "requires": { - "async-limiter": "~1.0.0" - } - }, "xdg-basedir": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-3.0.0.tgz", "integrity": "sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ=", "dev": true }, - "xml-name-validator": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", - "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==" - }, "xmldom": { "version": "0.1.27", "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.1.27.tgz", @@ -8809,32 +8361,6 @@ "lodash.isequal": "^4.5.0", "validator": "^10.11.0" } - }, - "zombie": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/zombie/-/zombie-6.1.4.tgz", - "integrity": "sha512-yxNvKtyz3PP8lkr31AYh7vdbBD4is9hYXiOQKPp+k/7GiDiFQXX1Ex+peCl4ttodu/bHZcIluJ8lxMla5XefBQ==", - "requires": { - "babel-runtime": "6.26.0", - "bluebird": "^3.5.1", - "debug": "^4.1.0", - "eventsource": "^1.0.5", - "iconv-lite": "^0.4.21", - "jsdom": "11.12.0", - "lodash": "^4.17.10", - "mime": "^2.3.1", - "ms": "^2.1.1", - "request": "^2.85.0", - "tough-cookie": "^2.3.4", - "ws": "^6.1.2" - }, - "dependencies": { - "mime": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz", - "integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==" - } - } } } } diff --git a/package.json b/package.json index aba6237..2b81f10 100644 --- a/package.json +++ b/package.json @@ -4,11 +4,11 @@ "description": "A Social platform for the creative at heart", "main": "./src/app.js", "scripts": { - "start": "npm run undoseeds && npm run migration && npm run seeds && babel-node ./src/app.js", + "start": "npm run undoseeds && npm run undomigration && npm run migration && npm run seeds && babel-node ./src/app.js", "seeds": "babel-node node_modules/.bin/sequelize db:seed:all --seeders-path src/seeders", "undoseeds": "babel-node node_modules/.bin/sequelize db:seed:undo:all", "migration": "babel-node node_modules/.bin/sequelize db:migrate", - "undomigration": "npm run undoseeds && babel-node node_modules/.bin/sequelize db:migrate:undo:all", + "undomigration": "babel-node node_modules/.bin/sequelize db:migrate:undo:all", "runmigrations": "npm run undoseeds && npm run migration && npm run seeds", "coveralls": "nyc report --reporter=text-lcov | coveralls", "test": "export NODE_ENV=test && npm run undomigration && npm run migration && npm run seeds && nyc --reporter=html --reporter=text mocha ./test --no-timeout --exit --require @babel/register", @@ -29,25 +29,20 @@ "bcrypt": "^3.0.6", "body-parser": "^1.19.0", "cloudinary": "^1.14.0", + "connect-multiparty": "^2.2.0", "cors": "^2.8.5", - "coverage": "^0.4.1", "dotenv": "^6.2.0", - "ejs": "^2.6.1", - "errorhandler": "^1.5.0", "express": "^4.17.1", "express-async-errors": "^3.1.1", - "express-jwt": "^5.3.1", "express-session": "^1.15.6", "joi": "^14.3.1", "jsonwebtoken": "^8.5.1", "method-override": "^2.3.10", - "methods": "^1.1.2", "multer": "^1.4.2", "nyc": "^14.1.1", "passport": "^0.4.0", "passport-facebook": "^3.0.0", "passport-google-oauth": "^2.0.0", - "passport-local": "^1.0.0", "passport-twitter": "^1.0.4", "pg": "^7.12.0", "pg-hstore": "^2.3.3", @@ -57,14 +52,11 @@ "slug": "^1.1.0", "swagger-jsdoc": "^3.3.0", "swagger-ui-express": "^4.0.7", - "underscore": "^1.9.1", - "winston": "^3.2.1", - "zombie": "^6.1.4", - "uniqid": "^5.0.3" + "uniqid": "^5.0.3", + "winston": "^3.2.1" }, "devDependencies": { "coveralls": "^3.0.5", - "mocha-lcov-reporter": "^1.3.0", "eslint": "^6.1.0", "eslint-config-airbnb-base": "^13.2.0", "eslint-plugin-import": "^2.18.2", diff --git a/src/controllers/profile.controller.js b/src/controllers/profile.controller.js new file mode 100644 index 0000000..9c13566 --- /dev/null +++ b/src/controllers/profile.controller.js @@ -0,0 +1,99 @@ +import cloudinary from 'cloudinary'; +import UserService from '../services/user.service'; +import Util from '../helpers/util'; + +const util = new Util(); + +/** + * @description User profile + */ +class Profile { + /** + * @description get user profile + * @param {object} req + * @param {object} res + * @returns {object} return object containing user profile + */ + static async getProfile(req, res) { + const userName = req.params.username; + + try { + let userfound; + if (userName) { + userfound = await UserService.findOne('', userName); + } else { + userfound = await UserService.findOne(req.auth.email, ''); + } + if (!userfound) { + util.setError(404, 'Profile not found'); + return util.send(res); + } + const data = { + username: userfound.username, + bio: userfound.bio, + image: userfound.image, + }; + util.setSuccess(200, 'Successfully retrieved a user profile', data); + return util.send(res); + } catch (err) { + util.setError(400, 'we are facing some problemsin system, please contact administrator'); + return util.send(res); + } + } + + /** + * @description update user profile + * @param {object} req + * @param {object} res + * @returns {object} return object containing updated user profile + */ + static async updateProfile(req, res) { + let filename = ''; + if (req.files.image) { + filename = req.files.image.path; + } + cloudinary.v2.uploader.upload(filename, { tags: 'CodepiratesAuthors-haven' }, async (err, image) => { + try { + const userName = req.auth.email; + const user = await UserService.findOne(userName, ''); + const oldURL = user.image; + if (!user) { + util.setError(400, 'User not found'); + return util.send(res); + } + const usernamefound = await UserService.getUserByUserName(req.body.username, userName); + if (usernamefound) { + util.setError(409, 'Sorry! The profile username taken, try another one'); + return util.send(res); + } + let imgURL; + if (!err) { + imgURL = image.secure_url; + } + if (!imgURL) { + imgURL = oldURL; + } + const prof = { + email: req.auth.email, + username: req.body.username, + bio: req.body.bio, + image: imgURL, + }; + const updateProfile = await UserService.updateProfile(prof); + const newProfile = { + userName: updateProfile[1].username, + email: updateProfile[1].email, + bio: updateProfile[1].bio, + image: updateProfile[1].image, + upadatedAt: updateProfile[1].upadatedAt, + }; + util.setSuccess(200, 'Successfully Profile Updated.', newProfile); + return util.send(res); + } catch (error) { + util.setError(400, 'we are having some problems in system, please contact administrator'); + return util.send(res); + } + }); + } +} +export default Profile; diff --git a/src/controllers/rating.controller.js b/src/controllers/rating.controller.js new file mode 100644 index 0000000..a0f9b2c --- /dev/null +++ b/src/controllers/rating.controller.js @@ -0,0 +1,129 @@ +import db from '../models/index'; +import RateService from '../services/rate.service'; +/** + * + * + * @class rateController + */ +class rateController { + /** + * + * + * @static + * @param {*} req + * @param {*} res + * @returns {Object} return rating information to user + * @memberof UserController + */ + static async setArticleRating(req, res) { + try { + // Initialize rating data + const userEmail = req.auth.email; + const { rate } = req.body; + const { articleSlug } = req.params; + const rateSchema = { userEmail, articleSlug, rate }; + + // check if user is trying to rate his/her own article + const article = await db.Article.findOne({ + where: { slug: articleSlug } + }); + const user = await db.user.findOne({ + where: { id: article.authorId } + }); + if (user.email === userEmail) { + return res.status(400).send({ + status: 400, + message: 'You cannot rate your own article' + }); + } + + // create rating + const createdRate = await RateService.create(rateSchema); + return res.status(200).send({ + status: 200, + message: 'Thank you for rating this article', + data: createdRate + }); + } catch (error) { + return res.status(404).send({ + status: 404, + message: error.message + }); + } + } + + /** + * + * + * @static + * @param {*} req + * @param {*} res + * @returns {Object} return rating information to user + * @memberof UserController + */ + static async updateArticleRating(req, res) { + try { + // Initialize rating data + const userEmail = req.auth.email; + const { rate } = req.body; + const { articleSlug } = req.params; + + // update article rate + const updatedRate = await RateService.update(articleSlug, userEmail, rate); + if (!updatedRate) { + return res.status(400).send({ + status: 400, + error: `Rating for article with Slug: ${articleSlug} not found` + }); + } + return res.status(200).send({ + status: 200, + message: 'Thank you for rating this article', + newRating: updatedRate + }); + } catch (error) { + return res.status(404).send({ + status: 404, + message: error.message + }); + } + } + + /** + * + * + * @static + * @param {*} req + * @param {*} res + * @returns {Object} return rating information to user + * @memberof rateController + */ + static async getArticleRating(req, res) { + try { + // Initialize rating data + const userEmail = req.auth.email; + const { articleSlug } = req.params; + + // find particular rating + const userRate = await RateService.findOne(articleSlug, userEmail); + if (!userRate) { + return res.status(400).send({ + status: 400, + error: `Rating for article with slug ${articleSlug} not found`, + }); + } + return res.status(200).send({ + status: 200, + message: `Rating for article with slug ${articleSlug} found`, + data: userRate + }); + } catch (error) { + return res.status(404).send({ + status: 404, + message: error.message + }); + } + } +} + +export default rateController; diff --git a/src/middlewares/validators/rate.validation.js b/src/middlewares/validators/rate.validation.js new file mode 100644 index 0000000..9bea05f --- /dev/null +++ b/src/middlewares/validators/rate.validation.js @@ -0,0 +1,21 @@ +import Joi from 'joi'; +import Util from '../../helpers/util'; + +const util = new Util(); + +export default (req, res, next) => { + const { rate } = req.body; + + const schema = { + rate: Joi.number().integer().min(1).max(5), + }; + const { error } = Joi.validate({ + rate + }, schema); + + if (!error) return next(); + const errorMessageFromJoi = error.details[0].message; + const removeEscapeCharacters = errorMessageFromJoi.replace(/['"]+/g, ''); + util.setError(400, removeEscapeCharacters); + return util.send(res); +}; diff --git a/src/middlewares/validators/user.profile.validator.js b/src/middlewares/validators/user.profile.validator.js new file mode 100644 index 0000000..b2908f3 --- /dev/null +++ b/src/middlewares/validators/user.profile.validator.js @@ -0,0 +1,38 @@ +import Joi from 'joi'; +import Util from '../../helpers/util'; + +const util = new Util(); + +export default (req, res, next) => { + const { + username, bio + } = req.body; + + const schema = { + username: Joi.string() + .trim() + .regex(/^[a-zA-Z]+$/) + .min(3) + .max(20), + bio: Joi.string() + .trim() + .min(20) + }; + const { error } = Joi.validate({ + bio, username + }, schema); + + if (!error) return next(); + const errorMessageFromJoi = error.details[0].message; + switch (errorMessageFromJoi) { + case `"bio" with value "${String(bio)}" fails to match the required pattern: /^[a-zA-Z]+$/`: + util.setError(400, 'bio should contain only characters'); + return util.send(res); + case `"username" with value "${String(username)}" fails to match the required pattern: /^[a-zA-Z]+$/`: + util.setError(400, 'username should contain only characters'); + return util.send(res); + default: + util.setError(400, errorMessageFromJoi); + return util.send(res); + } +}; diff --git a/src/migrations/20190805142252-create-user.js b/src/migrations/20190805142252-create-user.js index df824b8..aa57a9c 100644 --- a/src/migrations/20190805142252-create-user.js +++ b/src/migrations/20190805142252-create-user.js @@ -18,7 +18,7 @@ module.exports = { }, email: { allowNull: false, - unique:true, + unique: true, type: Sequelize.STRING }, password: { @@ -28,12 +28,20 @@ module.exports = { username: { allowNull: false, type: Sequelize.STRING, - unique:true + unique: true + }, + bio: { + allowNull: true, + type: Sequelize.STRING, + }, + image: { + allowNull: true, + type: Sequelize.STRING, }, role: { allowNull: false, type: Sequelize.STRING, - defaultValue:'normal' + defaultValue: 'normal' }, verified: { allowNull: false, @@ -53,4 +61,4 @@ module.exports = { down: (queryInterface, Sequelize) => { return queryInterface.dropTable('users'); } -}; \ No newline at end of file +}; diff --git a/src/migrations/20190814093719-create-rate.js b/src/migrations/20190814093719-create-rate.js new file mode 100644 index 0000000..57506f2 --- /dev/null +++ b/src/migrations/20190814093719-create-rate.js @@ -0,0 +1,47 @@ +'use strict'; +module.exports = { + up: (queryInterface, Sequelize) => { + return queryInterface.createTable('Rates', { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: Sequelize.INTEGER + }, + userEmail: { + type: Sequelize.STRING, + references: { + model: 'users', + key: 'email', + }, + onDelete: 'CASCADE', + onUpdate: 'CASCADE' + }, + articleSlug: { + type: Sequelize.STRING, + references: { + model: 'Articles', + key: 'slug', + }, + onDelete: 'CASCADE', + onUpdate: 'CASCADE' + }, + rate: { + type: Sequelize.ENUM({ + values: [1, 2, 3, 4, 5] + }) + }, + createdAt: { + allowNull: false, + type: Sequelize.DATE + }, + updatedAt: { + allowNull: false, + type: Sequelize.DATE + } + }); + }, + down: (queryInterface, Sequelize) => { + return queryInterface.dropTable('Rates'); + } +}; diff --git a/src/models/article.js b/src/models/article.js index e1d13a7..99c89b1 100644 --- a/src/models/article.js +++ b/src/models/article.js @@ -1,20 +1,20 @@ module.exports = (sequelize, DataTypes) => { const Article = sequelize.define('Article', { - slug: {type: DataTypes.STRING, allowNull: false}, - title: {type: DataTypes.STRING, allowNull: false}, - description: {type: DataTypes.TEXT, allowNull: false}, - body: {type: DataTypes.TEXT, allowNull: false}, - taglist: {type: DataTypes.ARRAY(DataTypes.STRING), allowNull: true}, - favorited: {type: DataTypes.BOOLEAN, allowNull: true, defaultValue: false}, - favoritedcount: {type: DataTypes.INTEGER, defaultValue: 0}, - flagged: {type: DataTypes.BOOLEAN, defaultValue: false}, - authorId: {type: DataTypes.INTEGER, allowNull: false}, - images: {type: DataTypes.ARRAY(DataTypes.STRING), allowNull: true}, + slug: { type: DataTypes.STRING, allowNull: false }, + title: { type: DataTypes.STRING, allowNull: false }, + description: { type: DataTypes.TEXT, allowNull: false }, + body: { type: DataTypes.TEXT, allowNull: false }, + taglist: { type: DataTypes.ARRAY(DataTypes.STRING), allowNull: true }, + favorited: { type: DataTypes.BOOLEAN, allowNull: true, defaultValue: false }, + favoritedcount: { type: DataTypes.INTEGER, defaultValue: 0 }, + flagged: { type: DataTypes.BOOLEAN, defaultValue: false }, + authorId: { type: DataTypes.INTEGER, allowNull: false }, + images: { type: DataTypes.ARRAY(DataTypes.STRING), allowNull: true }, views: DataTypes.INTEGER }, {}); - Article.associate = function(models) { + Article.associate = function (models) { Article.belongsTo(models.user, { - as:'author', + as: 'author', targetKey: 'id', onUpdate: 'CASCADE', onDelete: 'CASCADE' diff --git a/src/models/rate.js b/src/models/rate.js new file mode 100644 index 0000000..e347d29 --- /dev/null +++ b/src/models/rate.js @@ -0,0 +1,35 @@ +'use strict'; +module.exports = (sequelize, DataTypes) => { + const Rate = sequelize.define('Rate', { + userEmail: { + type: DataTypes.STRING, + references: { + model: 'users', + key: 'email', + }, + onDelete: 'CASCADE', + onUpdate: 'CASCADE' + }, + articleSlug: { + type: DataTypes.STRING, + references: { + model: 'Articles', + key: 'slug', + }, + onDelete: 'CASCADE', + onUpdate: 'CASCADE' + }, + rate: DataTypes.ENUM({ + values: [1, 2, 3, 4, 5] + }) + }, {}); + Rate.associate = function (models) { + Rate.belongsTo(models.user, { + foreignKey: 'userEmail', + }); + Rate.belongsTo(models.Article, { + foreignKey: 'articleSlug', + }); + }; + return Rate; +}; diff --git a/src/models/user.js b/src/models/user.js index c91a2c4..ec71a97 100644 --- a/src/models/user.js +++ b/src/models/user.js @@ -6,22 +6,26 @@ module.exports = (sequelize, DataTypes) => { email: DataTypes.STRING, password: DataTypes.STRING, username: DataTypes.STRING, - role: {type:DataTypes.STRING, defaultValue: 'normal'}, - verified: {type: DataTypes.BOOLEAN, defaultValue: false}, + image: DataTypes.STRING, + bio: DataTypes.STRING, + role: { type: DataTypes.STRING, defaultValue: 'normal' }, + verified: { type: DataTypes.BOOLEAN, defaultValue: false }, + role: { type: DataTypes.STRING, defaultValue: 'normal' }, + verified: { type: DataTypes.BOOLEAN, defaultValue: false }, }, {}); - - user.associate = ({ - Follow,Article - - }) => { - user.hasMany(Follow, { - foreignKey: 'followerId', - as: 'followerDetails' - }); - user.hasMany(Article, { - as:'author', - foreignKey:'authorId' - }) - }; + + user.associate = ({ + Follow, Article + + }) => { + user.hasMany(Follow, { + foreignKey: 'followerId', + as: 'followerDetails' + }); + user.hasMany(Article, { + as: 'author', + foreignKey: 'authorId' + }) + }; return user; }; diff --git a/src/routes/api/index.route.js b/src/routes/api/index.route.js index a4ef427..eee8499 100644 --- a/src/routes/api/index.route.js +++ b/src/routes/api/index.route.js @@ -1,17 +1,23 @@ import express from 'express'; import swaggerui from 'swagger-ui-express'; +import path from 'path'; import swaggerSpecification from '../../config/swagger'; - import oauth from './oauth/oauth.routes'; import user from './user/user.route'; import article from './article/article.routes'; +import profile from './profile/profile.route'; +import rate from './rate/rate.route'; const router = express.Router(); +router.use('/images', express.static(path.join(__dirname, 'images'))); router.use(oauth); +router.use('/profile', profile); router.use('/users', user); router.use('/', article); +router.use('/rate', rate); router.use('/api-docs', swaggerui.serve, swaggerui.setup(swaggerSpecification)); + export default router; diff --git a/src/routes/api/profile/profile.route.js b/src/routes/api/profile/profile.route.js new file mode 100644 index 0000000..0866939 --- /dev/null +++ b/src/routes/api/profile/profile.route.js @@ -0,0 +1,18 @@ +import express from 'express'; +import connectmultiparty from 'connect-multiparty'; +import ProfileController from '../../../controllers/profile.controller'; +import validateToken from '../../../middlewares/auth'; +import profileVAlidator from '../../../middlewares/validators/user.profile.validator'; +import confirmEmaiAuth from '../../../middlewares/emailVarification.middleware'; + + +const connectMulti = connectmultiparty(); +const router = express.Router(); +// greate edit a viewing profile handlers + +router.get('/', validateToken, confirmEmaiAuth, ProfileController.getProfile); +router.get('/:username', validateToken, confirmEmaiAuth, ProfileController.getProfile); +router.put('/', [validateToken, confirmEmaiAuth, connectMulti, profileVAlidator], ProfileController.updateProfile); + + +export default router; diff --git a/src/routes/api/rate/doc.js b/src/routes/api/rate/doc.js new file mode 100644 index 0000000..c697f6b --- /dev/null +++ b/src/routes/api/rate/doc.js @@ -0,0 +1,88 @@ +/** + * @swagger + * + * /rate/{articleSlug}: + * get: + * summary: Gets a rating + * description: > + * Get a rating about a specific article + * produces: + * - application/json + * parameters: + * - name: articleSlug + * in: path + * description: Slug for article being rated + * type: string + * required: true + * responses: + * 200: + * description: OK + * schema: + * $ref: '#definitions/Rate' + * patch: + * summary: updates a rating + * description: > + * updates a rating about a specific article + * produces: + * - application/json + * parameters: + * - name: articleSlug + * in: path + * description: Slug for article being rated + * type: string + * required: true + * - name: rate + * in: body + * description: article rating + * schema: + * type: object + * required: + * - rate + * properties: + * rate: + * type: string + * responses: + * 200: + * description: Rate successfully create + * schema: + * $ref: '#definitions/Rate' + * 400: + * description: input Validation error + * post: + * summary: creates a rating + * description: > + * creates a rating about a specific article + * produces: + * - application/json + * parameters: + * - name: articleSlug + * in: path + * description: Slug for article being rated + * type: string + * required: true + * - name: rate + * in: body + * description: article rating + * schema: + * type: object + * required: + * - rate + * properties: + * rate: + * type: string + * responses: + * 200: + * description: Rate successfully create + * schema: + * $ref: '#definitions/Rate' + * 400: + * description: input Validation error + * definitions: + * Rate: + * type: object + * properties: + * rate: + * type: string + * required: + * - rate + */ diff --git a/src/routes/api/rate/rate.route.js b/src/routes/api/rate/rate.route.js new file mode 100644 index 0000000..581d3cd --- /dev/null +++ b/src/routes/api/rate/rate.route.js @@ -0,0 +1,13 @@ +import express from 'express'; +import validateToken from '../../../middlewares/auth'; +import confirmEmaiAuth from '../../../middlewares/emailVarification.middleware'; +import rateController from '../../../controllers/rating.controller'; +import sanitizeRate from '../../../middlewares/validators/rate.validation'; + +const router = express.Router(); + +router.get('/:articleSlug', [validateToken, confirmEmaiAuth], rateController.getArticleRating); +router.post('/:articleSlug', [validateToken, sanitizeRate, confirmEmaiAuth], rateController.setArticleRating); +router.patch('/:articleSlug', [validateToken, sanitizeRate, confirmEmaiAuth], rateController.updateArticleRating); + +export default router; diff --git a/src/routes/api/user/user.route.js b/src/routes/api/user/user.route.js index eefdd77..4a1a28b 100644 --- a/src/routes/api/user/user.route.js +++ b/src/routes/api/user/user.route.js @@ -10,6 +10,7 @@ import followController from '../../../controllers/follow.controller'; import resetPasswordValidation from '../../../middlewares/validators/resetpassword.validation'; const router = express.Router(); + router.get('/verify', verifyEmail); router.get('/allusers', [validateToken, admin, confirmEmaiAuth], UserController.getAllUsers); router.post('/signup', validateUser, UserController.signup); @@ -23,6 +24,7 @@ router.post('/profiles/:userId/follow', [validateToken, validateUserId], followC router.get('/profiles/following', validateToken, followController.listOfFollowedUsers); router.get('/profiles/followers', validateToken, followController.listOfFollowers); + // reset password route handlers router.post('/reset', UserController.requestPasswordReset); router.patch('/reset/:token', resetPasswordValidation, UserController.handlePasswordReset); diff --git a/src/seeders/20190814094408-demo-article.js b/src/seeders/20190814094408-demo-article.js new file mode 100644 index 0000000..494a958 --- /dev/null +++ b/src/seeders/20190814094408-demo-article.js @@ -0,0 +1,25 @@ +export default { + up: queryInterface => queryInterface.bulkInsert( + 'Articles', + [ + + { + slug: 'fakeslug', + title: 'faketitle', + description: 'fakedescription', + body: 'fakebody', + taglist: ['asdf', 'sdfsf'], + favorited: true, + favoritedcount: 12, + flagged: true, + authorId: 1, + images: ['dddd'], + views: 4, + createdAt: new Date(), + updatedAt: new Date() + } + ], + {} + ), + down: queryInterface => queryInterface.bulkDelete('Articles', null, {}) +}; diff --git a/src/services/rate.service.js b/src/services/rate.service.js new file mode 100644 index 0000000..3165754 --- /dev/null +++ b/src/services/rate.service.js @@ -0,0 +1,91 @@ +import database from '../models/index'; + +/** + * + * + * @class RateService + */ +class RateService { + /** + * + * + * @static + * @param {*} articleSlug + * @param {*} userEmail + * @returns + * @memberof RateService + * @returns {Object} return db result object + */ + static async findOne(articleSlug, userEmail) { + try { + return await database.Rate.findOne({ + where: { articleSlug, userEmail } + }); + } catch (error) { + throw error; + } + } + + /** + * + * + * @static + * @returns + * @memberof RateService + * @returns {Object} return db result object + */ + static async getAllRatings() { + try { + return await database.Rate.findAll(); + } catch (error) { + throw error; + } + } + + /** + * + * + * @static + * @param {*} rate + * @returns + * @memberof RateService + * @returns {Object} return db result object + */ + static async create(rate) { + try { + return await database.Rate.create(rate); + } catch (error) { + throw error; + } + } + + /** + * + * + * @static + * @param {*} articleSlug + * @param {*} userEmail + * @param {*} updateRate + * @returns + * @memberof RateService + * @returns {Object} return db result object + */ + static async update(articleSlug, userEmail, updateRate) { + try { + const rateToUpdate = await database.Rate.findOne({ + where: { articleSlug } + }); + + if (rateToUpdate) { + await database.user.update(updateRate, { where: { articleSlug, userEmail } }); + + return updateRate; + } + return null; + } catch (error) { + throw error; + } + } +} + +export default RateService; diff --git a/src/services/user.service.js b/src/services/user.service.js index 4a0624f..ae8b9af 100644 --- a/src/services/user.service.js +++ b/src/services/user.service.js @@ -114,6 +114,32 @@ class UserService { } } + /** + * + * + * @static + * @param {*} userName + * @param {*} email + * @returns + * @memberof UserService + * @returns {Object} return db result object + */ + static async getUserByUserName(userName, email) { + try { + const theUser = await database.user.findOne({ + where: { + username: String(userName), + email: { + [Op.ne]: String(email) + } + } + }); + return theUser; + } catch (error) { + throw error; + } + } + /** * * @@ -164,6 +190,30 @@ class UserService { throw error; } } + + /** + * + * + * @static + * @param {*} profile + * @returns {Object} return db result object + * @memberof UserService + */ + static async updateProfile(profile) { + try { + const { email } = profile; + return await database.user.update( + { + username: profile.username, + bio: profile.bio, + image: profile.image + }, + { where: { email }, returning: true, plain: true } + ); + } catch (error) { + throw error; + } + } } export default UserService; diff --git a/test/rates.test.js b/test/rates.test.js new file mode 100644 index 0000000..4a88ef4 --- /dev/null +++ b/test/rates.test.js @@ -0,0 +1,118 @@ +import { chai, server, expect } from './test-setup'; + +let adminToken; +let userToken; +describe('Users', () => { + before('Login User', (done) => { + chai + .request(server) + .post('/api/v1/users/login') + .send({ + email: 'admin@gmail.com', + password: 'ASqw12345' + }) + .set('Accept', 'Application/JSON') + .end((error, res) => { + adminToken = `Bearer ${res.body.token}`; + }); + chai + .request(server) + .post('/api/v1/users/login') + .send({ + email: 'user@gmail.com', + password: 'ASqw12345' + }) + .set('Accept', 'Application/JSON') + .end((error, res) => { + userToken = `Bearer ${res.body.token}`; + done(); + }); + }); + + describe('/POST rate article', () => { + it('should be able to rate article', (done) => { + chai + .request(server) + .post('/api/v1/rate/fakeslug') + .set('Authorization', userToken) + .send({ + rate: 1 + }) + .set('Accept', 'Application/JSON') + .end((error, res) => { + expect(res.status).to.be.equal(200); + expect(res.body).to.have.deep.property('message', 'Thank you for rating this article'); + done(); + }); + }); + it('should return bad request type of error if a validation error occurs', (done) => { + chai + .request(server) + .post('/api/v1/rate/fakeslug') + .set('Authorization', userToken) + .send({ + rate: 8 + }) + .set('Accept', 'Application/JSON') + .end((error, res) => { + expect(res.status).to.be.equal(400); + done(); + }); + }); + it('should return bad request type of error if a article owner tries to rate his/her own article', (done) => { + chai + .request(server) + .post('/api/v1/rate/fakeslug') + .set('Authorization', adminToken) + .send({ + rate: 4 + }) + .set('Accept', 'Application/JSON') + .end((error, res) => { + expect(res.status).to.be.equal(400); + done(); + }); + }); + }); + describe('/PATCH rate article', () => { + it('should be able to rate article', (done) => { + chai + .request(server) + .patch('/api/v1/rate/fakeslug') + .set('Authorization', userToken) + .send({ + rate: 3 + }) + .set('Accept', 'Application/JSON') + .end((error, res) => { + expect(res.status).to.be.equal(200); + expect(res.body).to.have.deep.property('message', 'Thank you for rating this article'); + done(); + }); + }); + }); + describe('/PATCH rate article', () => { + it('should be able to rate article', (done) => { + chai + .request(server) + .get('/api/v1/rate/fakeslug') + .set('Authorization', userToken) + .end((error, res) => { + expect(res.status).to.be.equal(200); + expect(res.body).to.have.deep.property('message', 'Rating for article with slug fakeslug found'); + done(); + }); + }); + it('should return error if rate not found', (done) => { + chai + .request(server) + .get('/api/v1/rate/fakeslug') + .set('Authorization', userToken) + .end((error, res) => { + expect(res.status).to.be.equal(200); + expect(res.body).to.have.deep.property('message', 'Rating for article with slug fakeslug found'); + done(); + }); + }); + }); +}); diff --git a/test/user.profile.test.js b/test/user.profile.test.js new file mode 100644 index 0000000..aa02a7b --- /dev/null +++ b/test/user.profile.test.js @@ -0,0 +1,151 @@ +import { chai, server, expect } from './test-setup'; + +let usertoken; + +describe('/Create Profile feature', () => { + it('should login', (done) => { + chai + .request(server) + .post('/api/v1/users/login') + .send({ + email: 'user@gmail.com', + password: 'ASqw12345' + }) + .end((error, res) => { + if (error) done(error); + usertoken = `Bearer ${res.body.token}`; + expect(res.status).to.be.equal(200); + expect(res.body).to.have.deep.property('message'); + done(); + }); + }); + + + it('should not update the profile when the username updated is not a string', (done) => { + chai + .request(server) + .put('/api/v1/profile') + .set('Authorization', usertoken) + .send({ + bio: 'j is demonstrating this Bio', + username: 123454 + }) + .end((error, res) => { + if (error) done(error); + expect(res).to.be.an('object'); + expect(res.status).to.equal(400); + expect(res.body) + .to.have.property('message') + .to.be.a('string'); + done(); + }); + }); + + it('should throw an error when user not authenticated ', (done) => { + chai + .request(server) + .put('/api/v1/profile') + .set('x-access-token', `21${usertoken}`) + .end((error, res) => { + if (error) done(error); + expect(res.status).to.be.equal(401); + expect(res.body).to.have.deep.property('message'); + done(); + }); + }); + + it('Should successfully retrieve user profile', (done) => { + chai.request(server) + .get('/api/v1/profile/admin') + .set('x-access-token', usertoken) + .end((error, res) => { + if (error) done(error); + expect(res).have.status(200); + expect(res).to.be.an('object'); + expect(res.body).to.have.keys('message', 'status', 'data'); + expect(res.body.message).to.deep.equal('Successfully retrieved a user profile'); + expect(res.body.data).to.have.keys('username', 'bio', 'image'); + done(); + }); + }); + it('should not update profile when username is taken', (done) => { + chai + .request(server) + .put('/api/v1/profile') + .set('Authorization', usertoken) + .field('bio', 'I am a software developer based in kigali, i like data science and AI') + .field('username', 'admin') + .end((error, res) => { + if (error) done(error); + expect(res).to.be.an('object'); + expect(res.status).to.equal(409); + expect(res.body.message).to.deep.equal('Sorry! The profile username taken, try another one'); + done(); + }); + }); + + it('should not update profile when there is invalid username', (done) => { + chai + .request(server) + .put('/api/v1/profile') + .set('Authorization', usertoken) + .field('bio', 'I am a software developer based in kigali, i like data science and AI') + .field('username', 12333) + .end((error, res) => { + expect(res).to.be.an('object'); + expect(res.status).to.equal(400); + expect(res.body).to.have.keys('status', 'message'); + expect(res.body.status).to.deep.equal('error'); + done(); + }); + }); + + it('should not update profile when there is invalid bio', (done) => { + chai + .request(server) + .put('/api/v1/profile') + .set('Authorization', usertoken) + .field('bio', 'I am ') + .field('username', 'salviosage') + .end((error, res) => { + expect(res).to.be.an('object'); + expect(res.status).to.equal(400); + expect(res.body).to.have.keys('status', 'message'); + expect(res.body.status).to.deep.equal('error'); + done(); + }); + }); + + it('should update the profile', (done) => { + chai + .request(server) + .put('/api/v1/profile') + .set('Authorization', usertoken) + .field('bio', 'I am a software developer based in kigali, i like data science and AI') + .field('username', 'salvi') + .end((error, res) => { + if (error) done(error); + expect(res).to.be.an('object'); + expect(res.status).to.equal(200); + expect(res.body.data) + .to.have.property('bio'); + expect(res.body.data) + .to.have.property('image'); + done(); + }); + }); + + it('Should not retrieve user profile when there is none', (done) => { + chai.request(server) + .get('/api/v1/profile/mikki') + .set('Authorization', usertoken) + .end((error, res) => { + if (error) done(error); + expect(res).have.status(404); + expect(res).to.be.an('object'); + expect(res.body).to.have.keys('message', 'status'); + expect(res.body.message).to.deep.equal('Profile not found'); + done(); + }); + }); +});