From e7f285b3b4ace59e7709f8a4cdf6dd0b5e5f1780 Mon Sep 17 00:00:00 2001 From: MITCHELL PATRICK Date: Thu, 22 Aug 2019 11:07:59 +0100 Subject: [PATCH] ft(table):integrate the requests table in the backend Created the controller and router setup to allow allow users retrieve travel requests. [Finishes #167727463] --- .babelrc | 4 +- .env.example | 21 +- .eslintignore | 9 + .eslintrc.json | 6 +- .mocharc.json | 2 +- .travis.yml | 4 +- Dockerfile | 4 +- package-lock.json | 281 +++++++++++++++++--------- package.json | 10 +- src/controllers/request.controller.js | 28 +++ src/controllers/user.controller.js | 7 + src/controllers/users.js | 4 +- src/index.js | 3 +- src/middlewares/auth.js | 67 +++--- src/routes/api/auth.router.js | 5 +- src/routes/api/index.js | 7 +- src/routes/api/requests.router.js | 9 + src/routes/index.js | 7 + src/test/index.test.js | 35 ++++ src/test/request.test.js | 95 +++++++++ src/test/signup.test.js | 46 +++-- src/utils/response.utils.js | 22 +- 22 files changed, 499 insertions(+), 177 deletions(-) create mode 100644 .eslintignore create mode 100644 src/controllers/request.controller.js create mode 100644 src/routes/api/requests.router.js create mode 100755 src/routes/index.js create mode 100644 src/test/index.test.js create mode 100644 src/test/request.test.js diff --git a/.babelrc b/.babelrc index 6cffe23..e4d99cd 100644 --- a/.babelrc +++ b/.babelrc @@ -1,3 +1,3 @@ - { +{ "presets": ["@babel/preset-env"] - } +} diff --git a/.env.example b/.env.example index 9845c02..76d7977 100644 --- a/.env.example +++ b/.env.example @@ -7,17 +7,28 @@ # Please take note that the dummy values below will not work. # They are just to show you what yours shoud look like. -DB_URI_DEVELOPMENT="https://devwebsite.com" +NODE_ENV=development + +DB_DIALECT=postgres + +DB_STRING_DEVELOPMENT=dev://dev:devpasswd:5555/ +DB_HOST_DEVELOPMENT=localhost +DB_NAME_DEVELOPMENT=db_dev DB_USER_DEVELOPMENT=devUser DB_PASSWORD_DEVELOPMENT=devPassword -DB_URI_STAGING="https://stagingWebsite.com" +DB_STRING_STAGING=staging://staginguser:stagingpasswd:5432/ +DB_HOST_STAGING=127.0.0.1 +DB_NAME_STAGING=db_staging DB_USER_STAGING=stagingUser DB_PASSWORD_STAGING=stagingUser -DB_URI_TEST="https://prodwebsite.com" -DB_USER_TEST=prodUser -DB_PASSWORD_TEST=prodPassword +DB_STRING_PRODUCTION="prod://produser:prodpasswd:3306/" +DB_HOST_PRODUCTION=localhost +DB_NAME_PRODUCTION=db_prod +DB_USER_PRODUCTION=stagingUser +DB_PASSWORD_PRODUCTION=stagingUser + ................. ................. diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..07de60c --- /dev/null +++ b/.eslintignore @@ -0,0 +1,9 @@ +src/test +.gitignore +HISTORY.txt +README.txt +node_modules/ +.env +app.js +.idea/ +dist/ diff --git a/.eslintrc.json b/.eslintrc.json index 8341716..670bd57 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -7,17 +7,21 @@ "mocha": true, "commonjs": true }, + "parser": "babel-eslint", "parserOptions": { "ecmaVersion": 6, "sourceType": "module", + "allowImportExportEverywhere": true, "ecmaFeatures": { "jsx": true } }, - "parser": "babel-eslint", "plugins": [ "flowtype" ], + "globals": { + "_": false + }, "rules": { "one-var": 0, "camelcase": 0, diff --git a/.mocharc.json b/.mocharc.json index 14c2251..69d2edc 100644 --- a/.mocharc.json +++ b/.mocharc.json @@ -1,3 +1,3 @@ { - "spec": "src/test" + "spec": "src/test" } diff --git a/.travis.yml b/.travis.yml index 9b01895..9e855ef 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ language: node_js node_js: - - "v10.15.3" + - 10.15.3 cache: directories: @@ -16,8 +16,10 @@ services: before_script: - psql -c "CREATE DATABASE barefootNomad_test;" -U postgres - psql -c "ALTER ROLE postgres PASSWORD 'postgres';" -U postgres + - touch src/logs/app.log script: + - npm run db:migrate - npm test after_success: diff --git a/Dockerfile b/Dockerfile index 05fe9db..5e7e54d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,9 +1,9 @@ FROM alpine:3.10.1 WORKDIR /app COPY package*.json ./ -RUN apk add --update nodejs npm python +RUN apk add --update alpine-sdk nodejs npm python RUN LD_LIBRARY_PATH=/usr/local/lib64/:$LD_LIBRARY_PATH && export LD_LIBRARY_PATH && npm ci COPY . . RUN npm run clean && npm run build EXPOSE 3000 -CMD ["npm", "run", "start:staging"] +CMD ["npm", "run", "start:dev"] diff --git a/package-lock.json b/package-lock.json index c35f9a4..348f682 100644 --- a/package-lock.json +++ b/package-lock.json @@ -914,6 +914,42 @@ "@sendgrid/helpers": "^6.4.0" } }, + "@sinonjs/commons": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.6.0.tgz", + "integrity": "sha512-w4/WHG7C4WWFyE5geCieFJF6MZkbW4VAriol5KlmQXpAQdxvV0p26sqNZOW6Qyw6Y0l9K4g+cHvvczR2sEEpqg==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + }, + "@sinonjs/formatio": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-3.2.1.tgz", + "integrity": "sha512-tsHvOB24rvyvV2+zKMmPkZ7dXX6LSLKZ7aOtXY6Edklp0uRcgGpOsQTTGTcWViFyx4uhWc6GV8QdnALbIbIdeQ==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1", + "@sinonjs/samsam": "^3.1.0" + } + }, + "@sinonjs/samsam": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-3.3.3.tgz", + "integrity": "sha512-bKCMKZvWIjYD0BLGnNrxVuw4dkWCYsLqFOUWw8VgKF/+5Y+mE7LfHWPIYoDXowH+3a9LsWDMo0uAP8YDosPvHQ==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.3.0", + "array-from": "^2.1.1", + "lodash": "^4.17.15" + } + }, + "@sinonjs/text-encoding": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz", + "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", + "dev": true + }, "@types/babel-types": { "version": "7.0.7", "resolved": "https://registry.npmjs.org/@types/babel-types/-/babel-types-7.0.7.tgz", @@ -1002,9 +1038,9 @@ } }, "acorn": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.3.0.tgz", - "integrity": "sha512-/czfa8BwS88b9gWQVhc8eknunSA2DoJpJyTQkhheIf5E48u1N0R4q/YxxsAeqRrmK9TQ/uYfgLDfZo91UlANIA==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.0.0.tgz", + "integrity": "sha512-PaF/MduxijYYt7unVGRuds1vBC9bFxbNf+VWqhOClfdgy7RlVkQqt610ig1/yxTgsDIfW1cWDel5EBbOy3jdtQ==", "dev": true }, "acorn-globals": { @@ -1023,9 +1059,9 @@ } }, "acorn-jsx": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.0.1.tgz", - "integrity": "sha512-HJ7CfNHrfJLlNTzIEUTj43LNWGkqpRLxm3YjAlcD0ACydk9XynzYsCBHxut+iqt+1aBXkx9UP/w/ZqMr13XIzg==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.0.2.tgz", + "integrity": "sha512-tiNTrP1MP0QrChmD2DdupCr6HWSFeKVw5d/dHTu4Y7rkAkRhU/Dt7dphAfIUyxtHpl/eBVip5uTNSpQJHylpAw==", "dev": true }, "ajv": { @@ -1108,13 +1144,10 @@ "dev": true }, "ansi-escapes": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.2.1.tgz", - "integrity": "sha512-Cg3ymMAdN10wOk/VYfLV7KCQyv7EDirJ64500sU7n9UlmioEtDuU5Gd+hj73hXSU/ex7tHJSssmyftDdkMLO8Q==", - "dev": true, - "requires": { - "type-fest": "^0.5.2" - } + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", + "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", + "dev": true }, "ansi-regex": { "version": "2.1.1", @@ -1221,6 +1254,12 @@ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" }, + "array-from": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/array-from/-/array-from-2.1.1.tgz", + "integrity": "sha1-z+nYwmYoudxa7MYqn12PHzUsEZU=", + "dev": true + }, "array-includes": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.0.3.tgz", @@ -1853,12 +1892,12 @@ } }, "cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", "dev": true, "requires": { - "restore-cursor": "^3.1.0" + "restore-cursor": "^2.0.0" } }, "cli-width": { @@ -2487,9 +2526,9 @@ "dev": true }, "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", "dev": true }, "enabled": { @@ -2607,9 +2646,9 @@ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, "eslint": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.1.0.tgz", - "integrity": "sha512-QhrbdRD7ofuV09IuE2ySWBz0FyXCq0rriLTZXZqaWSI79CVtHVRdkFuFTViiqzZhkCgfOh9USpriuGN2gIpZDQ==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.2.2.tgz", + "integrity": "sha512-mf0elOkxHbdyGX1IJEUsNBzCDdyoUgljF3rRlgfyYh0pwGnreLc0jjD6ZuleOibjmnUWZLY2eXwSooeOgGJ2jw==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", @@ -2619,9 +2658,9 @@ "debug": "^4.0.1", "doctrine": "^3.0.0", "eslint-scope": "^5.0.0", - "eslint-utils": "^1.3.1", - "eslint-visitor-keys": "^1.0.0", - "espree": "^6.0.0", + "eslint-utils": "^1.4.2", + "eslint-visitor-keys": "^1.1.0", + "espree": "^6.1.1", "esquery": "^1.0.1", "esutils": "^2.0.2", "file-entry-cache": "^5.0.1", @@ -2854,14 +2893,14 @@ "integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==" }, "espree": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-6.0.0.tgz", - "integrity": "sha512-lJvCS6YbCn3ImT3yKkPe0+tJ+mH6ljhGNjHQH9mRtiO6gjhVAOhVXW1yjnwqGwTkK3bGbye+hb00nFNmu0l/1Q==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-6.1.1.tgz", + "integrity": "sha512-EYbr8XZUhWbYCqQRW0duU5LxzL5bETN6AjKBGy1302qqzPaCH10QbRg3Wvco79Z8x9WbiE8HYB4e75xl6qUYvQ==", "dev": true, "requires": { - "acorn": "^6.0.7", - "acorn-jsx": "^5.0.0", - "eslint-visitor-keys": "^1.0.0" + "acorn": "^7.0.0", + "acorn-jsx": "^5.0.2", + "eslint-visitor-keys": "^1.1.0" } }, "esprima": { @@ -3213,9 +3252,9 @@ "integrity": "sha512-lUGBnIamTAwk4znq5BcqsDaxSmZ9nDVJaij6NvRt/Tg4R69gERA+otPKbS86ROw9nxVMw2/mp1fnaiWqbs6Sdg==" }, "figures": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.0.0.tgz", - "integrity": "sha512-HKri+WoWoUgr83pehn/SIgLOMZ9nAWC6dcGj26RY2R4F50u4+RTUz0RCrUlOV3nKRAICW1UGzyb+kcX2qK1S/g==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", "dev": true, "requires": { "escape-string-regexp": "^1.0.5" @@ -4409,22 +4448,22 @@ "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" }, "inquirer": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.5.1.tgz", - "integrity": "sha512-uxNHBeQhRXIoHWTSNYUFhQVrHYFThIt6IVo2fFmSe8aBwdR3/w6b58hJpiL/fMukFkvGzjg+hSxFtwvVmKZmXw==", + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.5.2.tgz", + "integrity": "sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ==", "dev": true, "requires": { - "ansi-escapes": "^4.2.1", + "ansi-escapes": "^3.2.0", "chalk": "^2.4.2", - "cli-cursor": "^3.1.0", + "cli-cursor": "^2.1.0", "cli-width": "^2.0.0", "external-editor": "^3.0.3", - "figures": "^3.0.0", - "lodash": "^4.17.15", - "mute-stream": "0.0.8", + "figures": "^2.0.0", + "lodash": "^4.17.12", + "mute-stream": "0.0.7", "run-async": "^2.2.0", "rxjs": "^6.4.0", - "string-width": "^4.1.0", + "string-width": "^2.1.0", "strip-ansi": "^5.1.0", "through": "^2.3.6" }, @@ -4603,9 +4642,9 @@ "dev": true }, "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", "dev": true }, "is-glob": { @@ -5023,6 +5062,12 @@ "promise": "^7.0.1" } }, + "just-extend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.0.2.tgz", + "integrity": "sha512-FrLwOgm+iXrPV+5zDU6Jqu4gCRXbWEQg2O3SKONsWE4w7AXFRkryS53bpWdaL9cNol+AmR3AEYz6kn+o0fCPnw==", + "dev": true + }, "jwa": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", @@ -5209,6 +5254,12 @@ } } }, + "lolex": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-4.2.0.tgz", + "integrity": "sha512-gKO5uExCXvSm6zbF562EvM+rd1kQDnB9AZBbiQVzf1ZmdDpxUSvpnAaVOP83N/31mRK8Ml8/VE8DMvsAZQ+7wg==", + "dev": true + }, "longest": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", @@ -5739,9 +5790,9 @@ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, "mute-stream": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", + "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", "dev": true }, "nan": { @@ -5828,6 +5879,36 @@ "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" }, + "nise": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/nise/-/nise-1.5.2.tgz", + "integrity": "sha512-/6RhOUlicRCbE9s+94qCUsyE+pKlVJ5AhIv+jEE7ESKwnbXqulKZ1FYU+XAtHHWE9TinYvAxDUJAb912PwPoWA==", + "dev": true, + "requires": { + "@sinonjs/formatio": "^3.2.1", + "@sinonjs/text-encoding": "^0.7.1", + "just-extend": "^4.0.2", + "lolex": "^4.1.0", + "path-to-regexp": "^1.7.0" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "path-to-regexp": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz", + "integrity": "sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=", + "dev": true, + "requires": { + "isarray": "0.0.1" + } + } + } + }, "node-environment-flags": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.6.tgz", @@ -6212,12 +6293,20 @@ "integrity": "sha1-+M33eISCb+Tf+T46nMN7HkSAdC4=" }, "onetime": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz", - "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", "dev": true, "requires": { - "mimic-fn": "^2.1.0" + "mimic-fn": "^1.0.0" + }, + "dependencies": { + "mimic-fn": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", + "dev": true + } } }, "optimist": { @@ -7213,12 +7302,12 @@ "dev": true }, "restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", + "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", "dev": true, "requires": { - "onetime": "^5.1.0", + "onetime": "^2.0.0", "signal-exit": "^3.0.2" } }, @@ -7494,6 +7583,21 @@ } } }, + "sinon": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-7.4.1.tgz", + "integrity": "sha512-7s9buHGHN/jqoy/v4bJgmt0m1XEkCEd/tqdHXumpBp0JSujaT4Ng84JU5wDdK4E85ZMq78NuDe0I3NAqXY8TFg==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.4.0", + "@sinonjs/formatio": "^3.2.1", + "@sinonjs/samsam": "^3.3.2", + "diff": "^3.5.0", + "lolex": "^4.2.0", + "nise": "^1.5.1", + "supports-color": "^5.5.0" + } + }, "slash": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", @@ -7509,14 +7613,6 @@ "ansi-styles": "^3.2.0", "astral-regex": "^1.0.0", "is-fullwidth-code-point": "^2.0.0" - }, - "dependencies": { - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - } } }, "snapdragon": { @@ -7781,29 +7877,28 @@ "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" }, "string-width": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.1.0.tgz", - "integrity": "sha512-NrX+1dVVh+6Y9dnQ19pR0pP4FiEIlUvdTGn8pw6CKTNq5sgib2nIhmUNT5TAmhWmvKr3WcxBcP3E8nWezuipuQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", "dev": true, "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^5.2.0" + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" }, "dependencies": { "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", "dev": true }, "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "dev": true, "requires": { - "ansi-regex": "^4.1.0" + "ansi-regex": "^3.0.0" } } } @@ -7886,6 +7981,16 @@ } } }, + "supertest": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/supertest/-/supertest-4.0.2.tgz", + "integrity": "sha512-1BAbvrOZsGA3YTCWqbmh14L0YEq0EGICX/nBnfkfVJn7SrxQV1I3pMYjSzG9y/7ZU2V9dWqyqk2POwxlb09duQ==", + "dev": true, + "requires": { + "methods": "^1.1.2", + "superagent": "^3.8.3" + } + }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -7925,18 +8030,6 @@ "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", "dev": true }, - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, "string-width": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", @@ -8239,12 +8332,6 @@ "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", "dev": true }, - "type-fest": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.5.2.tgz", - "integrity": "sha512-DWkS49EQKVX//Tbupb9TFa19c7+MK1XmzkrZUR8TAktmE/DizXoaoJV6TZ/tSIPXipqNiRI6CyAe7x69Jb6RSw==", - "dev": true - }, "type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", diff --git a/package.json b/package.json index cb9fcf7..a099bfa 100755 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "docker:rebuild": "docker-compose up --rebuild", "docker:destroy": "docker-compose down -v", "lint": "eslint ./src --fix", - "nyc": "nyc mocha -r esm --cache false --timeout 3000 --exit", + "nyc": "nyc mocha -r esm --cache false --timeout 15000 --exit", "db:migrate": "sequelize db:migrate", "db:seed": "sequelize db:seed:all --debug", "undo:migration": "sequelize db:migrate:undo:all", @@ -26,7 +26,9 @@ }, "nyc": { "exclude": [ - "src/index.js" + "src/index.js", + "src/db/*", + "src/routes/api/index.js" ] }, "author": "Andela Simulations Programme", @@ -81,6 +83,8 @@ "eslint-plugin-import": "^2.18.2", "mocha": "^6.1.4", "nodemon": "^1.19.1", - "nyc": "^14.1.1" + "nyc": "^14.1.1", + "sinon": "^7.4.1", + "supertest": "^4.0.2" } } diff --git a/src/controllers/request.controller.js b/src/controllers/request.controller.js new file mode 100644 index 0000000..fc13b68 --- /dev/null +++ b/src/controllers/request.controller.js @@ -0,0 +1,28 @@ +import models from '../db/models'; +import Response from '../utils/response.utils'; +const { Request } = models; + +/** + * This class creates the request controller + */ +export default class RequestController { + /** + * @param {Object} req The user's token which is decoded to get the user's id + * @param {Object} res List of request returned to the user + * @returns {array} An array of objects or an empty one + */ + static async findAll(req, res) { + // const requestData; + + await Request.findAll({ where: { user_id: req.body.user_id }}) + .then(data => { + if (data.length) { + return Response.Success(res, data, 200); + } + return Response.CustomError(res, 404, 'error', 'No requests found'); + }) + .catch((err) => { + return Response.InternalServerError(res, err); + }); + } +} diff --git a/src/controllers/user.controller.js b/src/controllers/user.controller.js index 945bf6b..bd29248 100644 --- a/src/controllers/user.controller.js +++ b/src/controllers/user.controller.js @@ -30,6 +30,13 @@ export default class UserController { await sender.sendEmail(process.env.SENDER_EMAIL, user.email, 'signup_template', payload); const userToken = JWTService.generateToken(user); + + res.cookie('token', userToken, { + expires: new Date(Date.now() + (604800 * 1000)), + httpOnly: true, + secure: true + }); + return Response.Success(res, { id: user.id, ...userData, diff --git a/src/controllers/users.js b/src/controllers/users.js index ebc0636..eb4b107 100644 --- a/src/controllers/users.js +++ b/src/controllers/users.js @@ -1,8 +1,8 @@ import bcrypt from 'bcrypt'; import models from '../db/models'; import token from '../services/tokenGenerator'; -import sender from '../services/email'; -import userSignupData from '../utils/user'; +import sender from '../services/email.service'; +import userSignupData from '../utils/user.utils'; /** * This class creates the user controller diff --git a/src/index.js b/src/index.js index 28f179a..51a2226 100755 --- a/src/index.js +++ b/src/index.js @@ -9,7 +9,7 @@ import cors from 'cors'; import swaggerUi from 'swagger-ui-express'; import logger from './logs/winston'; import swaggerDocument from '../swagger.json'; -import v1Router from './routes/api'; +import v1Router from './routes'; const app = express(); @@ -20,7 +20,6 @@ app.use((req, res, next) => { }); app.use(express.json()); -app.use(express.urlencoded({ extended: false })); app.use(cors()); app.use(bodyParser.urlencoded({ extended: true })); app.use(bodyParser.json()); diff --git a/src/middlewares/auth.js b/src/middlewares/auth.js index d34e374..56b06d8 100644 --- a/src/middlewares/auth.js +++ b/src/middlewares/auth.js @@ -1,51 +1,44 @@ import jwt from 'jsonwebtoken'; -const auth = { - verifyToken(token) { - let decoded = {}; - try { - decoded.payload = jwt.verify(token, process.env.JWT_SECRET); - } catch (error) { - decoded = { error: error.message }; +export default { + verifyToken: (req, res, next) => { + const { authorization } = req.headers; + let token; + + if (typeof authorization !== 'undefined' && authorization.includes('Bearer')) { + token = authorization.replace('Bearer ', ''); + } else { + token = req.body.token; } - return decoded; - }, - async verifyUserToken(req, res, next) { - try { - let token; + if (typeof token === 'undefined') { + return res.status(403).json({ + status: 'error', + error: { + message: 'You must be logged in to proceed' + }, + }); + } - if (req.headers.authorization) { - [, token] = req.headers.authorization.split(' '); - } else if (req.headers['x-access-token']) { - token = req.headers['x-access-token']; - } else if (req.headers.token) { - token = req.headers.token; - } + try { + req.user = jwt.decode(token, process.env.JWT_SECRET); - const decoded = auth.verifyToken(token); + const { + id, role + } = req.user; - if (!token) { - return res.status(401).json({ - status: 'error', - error: 'No token provided.', - }); - } + /* pipe the token details into the request body */ + req.body.user_id = id; + req.body.role = role; - if (decoded.error) { - return res.status(401).json({ - status: 'error', - error: 'Failed to authenticate token.', - }); - } return next(); } catch (error) { - return res.status(500).json({ + return res.status(400).json({ status: 'error', - error: 'Internal Server Error', + error: { + message: 'Authentication failed!', + }, }); } - } + }, }; - -export default auth; diff --git a/src/routes/api/auth.router.js b/src/routes/api/auth.router.js index a22f47f..7c7c303 100644 --- a/src/routes/api/auth.router.js +++ b/src/routes/api/auth.router.js @@ -1,13 +1,12 @@ import express from 'express'; - import UserController from '../../controllers/user.controller'; import UserMiddleware from '../../middlewares/user.middleware'; const router = express.Router(); /* Users Routes Here */ -router.post('/signup', UserController.signup); -router.post('/signin', ...UserMiddleware.validateSigninFields(), +router.post('/auth/signup', UserController.signup); +router.post('/auth/signin', ...UserMiddleware.validateSigninFields(), UserController.signin); export default router; diff --git a/src/routes/api/index.js b/src/routes/api/index.js index 055e4e7..b0e96dc 100755 --- a/src/routes/api/index.js +++ b/src/routes/api/index.js @@ -1,10 +1,9 @@ import express from 'express'; -import authRouter from './auth.router'; - - const router = express.Router(); -router.use('/auth', authRouter); +router.get('/', (req, res) => { + res.status(200).json({ Message: 'Welcome! This is the NorthStar Barefoot Nomad homepage' }); +}); export default router; diff --git a/src/routes/api/requests.router.js b/src/routes/api/requests.router.js new file mode 100644 index 0000000..78994e1 --- /dev/null +++ b/src/routes/api/requests.router.js @@ -0,0 +1,9 @@ +import express from 'express'; +import RequestController from '../../controllers/request.controller'; +import Auth from '../../middlewares/auth'; + +const router = express.Router(); + +router.get('/requests', Auth.verifyToken, RequestController.findAll); + +export default router; diff --git a/src/routes/index.js b/src/routes/index.js new file mode 100755 index 0000000..bd18107 --- /dev/null +++ b/src/routes/index.js @@ -0,0 +1,7 @@ +import userRouter from './api/auth.router'; +import RequestsRoutes from './api/requests.router'; +import IndexRoutes from './api/index'; + +const router = [RequestsRoutes, userRouter, IndexRoutes]; + +export default router; diff --git a/src/test/index.test.js b/src/test/index.test.js new file mode 100644 index 0000000..d48b5be --- /dev/null +++ b/src/test/index.test.js @@ -0,0 +1,35 @@ +import chai from 'chai'; +import chaiHttp from 'chai-http'; +import app from '../index'; + +chai.use(chaiHttp); +chai.should(); + +describe('/GET Indexing', () => { + it ('return success on page load', (done) => { + chai.request(app) + .get('/') + .end((err, res) => { + res.should.have.status(200); + done(); + }) + }); + + it ('return failure on nonexistent route', (done) => { + chai.request(app) + .get('/nonexistent') + .end((err, res) => { + res.should.have.status(404); + done(); + }) + }); + + it ('return failure on nonexistent route v1', (done) => { + chai.request(app) + .get('/api/v1/nonexistent') + .end((err, res) => { + res.should.have.status(404); + done(); + }) + }); +}); diff --git a/src/test/request.test.js b/src/test/request.test.js new file mode 100644 index 0000000..45cde7d --- /dev/null +++ b/src/test/request.test.js @@ -0,0 +1,95 @@ +import chai from 'chai'; +import chaiHttp from 'chai-http'; +import app from '../index'; +import sinon from 'sinon'; +import RequestController from '../controllers/request.controller'; + +chai.use(chaiHttp); +chai.should(); +const { expect } = chai; + +const requestEndpoint = '/api/v1/requests'; +const loginEndpoint = '/api/v1/auth/signin'; + +// lets signin the user first. +const user = { + email: 'john_doe@email.com', + password: 'qwertyuiop' +}; + +describe('/GET REQUESTS', () => { + + it ('should login and return the token', (done) => { + chai.request(app) + .post(loginEndpoint) + .send(user) + .end((err, res) => { + user.token = res.body.data.token; + res.should.have.status(200); + done(); + }) + }); + + // now for the main tests // + it ('should ask for token if there is none', (done) => { + chai.request(app) + .get(requestEndpoint) + .set('Authorization', '') + .end((err, res) => { + res.should.have.status(403); + done(); + }) + }); + + it ('should return 200 if requests are found', (done) => { + const validToken = `Bearer ${user.token}`; + chai.request(app) + .get(requestEndpoint) + .set('Authorization', validToken) + .end((err, res) => { + res.should.have.status(200); + done(); + }) + }); + + it ('return error if token is invalid/expired', (done) => { + const invalidToken = `Bearer 000!!000xx0##^&##${user.token}xx000xxx000xxx0000`; + chai.request(app) + .get(requestEndpoint) + .set('Authorization', invalidToken) + .end((err, res) => { + res.should.have.status(400); + done(); + }) + }); + + it ('return failure on nonexistent route v1', (done) => { + chai.request(app) + .get(`${requestEndpoint}nonexistent`) + .end((err, res) => { + res.should.have.status(404); + done(); + }) + }); + + describe("test500 error", function() { + it("should return error 500 page", () => { + let req = {}; + let res = { + send: sinon.spy() + }; + + RequestController.findAll(req, res); + expect(res.send.calledOnce).to.be.false; + }); + }); + + describe('Sample Sinon Stub', () => { + it('should pass', (done) => { + const greaterThanTwenty = sinon.stub().returns('something'); + greaterThanTwenty(0).should.eql('something'); + greaterThanTwenty(0).should.not.eql(false); + done(); + }); + }); +}); diff --git a/src/test/signup.test.js b/src/test/signup.test.js index e256b7c..4c7006b 100644 --- a/src/test/signup.test.js +++ b/src/test/signup.test.js @@ -19,22 +19,36 @@ const user = { }; describe('Users', () => { - // Test for creating new user - describe('/POST register users', () => { - it('it should Signup a user and generate a token', (done) => { - chai.request(app) - .post('/api/v1/auth/signup') - .send(user) - .end((err, res) => { - res.should.have.status(201); - res.body.should.be.an('object'); - res.body.should.have.property('status').eql('success'); - res.body.should.have.property('data'); - res.body.data.should.have.property('first_name'); - res.body.data.should.have.property('last_name'); - res.body.data.should.have.property('email'); - done(); + // Test for creating new user + describe('/POST register users', () => { + it('it should Signup a user and generate a token', (done) => { + chai.request(app) + .post('/api/v1/auth/signup') + .send(user) + .end((err, res) => { + user.token = res.body.data.token; + res.should.have.status(201); + res.body.should.be.an('object'); + res.body.should.have.property('status').eql('success'); + res.body.should.have.property('data'); + res.body.data.should.have.property('first_name'); + res.body.data.should.have.property('last_name'); + res.body.data.should.have.property('email'); + done(); + }); + }); + }); + + describe('/GET Requests', () => { + it('should return 404 on zero requests found', (done) => { + const validToken = `Bearer ${user.token}`; + chai.request(app) + .get('/api/v1/requests') + .set('Authorization', validToken) + .end((err, res) => { + res.should.have.status(404); + done(); + }) }); }); - }); }); diff --git a/src/utils/response.utils.js b/src/utils/response.utils.js index 6edb327..5d7abd0 100644 --- a/src/utils/response.utils.js +++ b/src/utils/response.utils.js @@ -18,7 +18,7 @@ export default class { /** * Defines the specification for the "success" response cases - * @param {ServerResponse} res + * @param {Object} res * @param {object} data * @param {number} code * @returns {ServerResponse} response @@ -41,4 +41,24 @@ export default class { error }); } + + /** + * Defines the specification for the "custom" response cases + * @param {Object} res + * @param {number} code + * @param {string} status + * @param {string} message + * @param information + * @returns {Object} response + */ + static CustomError(res, code, status, message, information=0) { + return res.status(code).json({ + status, + error: { + message, + information + } + }); + } } +