diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..1644687 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,24 @@ +node_modules + +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +npm-debug.log + +Dockerfile* +.docker-compose* +.dockerignore + +.git +.gitignore + +.env.example +*.example.json + +*.md +!README.md +LICENSE +.vscode \ No newline at end of file diff --git a/.env.example b/.env.example index 40dff42..4b0d61a 100644 --- a/.env.example +++ b/.env.example @@ -1,16 +1,35 @@ -API_URL=http://localhost:8000 -CLIENT_URL=http://localhost:3000 -CORS_ORIGIN=http://localhost:3000 +APP_SECRET=something_secret +SESSION_SECRET=something_secret +# Provide service_name for DB_HOST while running in docker, otherwise use localhost +DB_HOST=localhost +# Provide exposed docker port while running in docker, otherwise use 27017 +DB_PORT=27017 +# DB_AUTH_ENABLED (true or false) +DB_AUTH_ENABLED=false +DB_AUTH_SOURCE=admin +DB_NAME=JSPF DB_USERNAME=db_username DB_PASSWORD=db_password -DB_URI=mongodb://localhost/JSPF -PORT=8000 +# Provide service_name to REDIS_HOST(redis) while running in docker +# While running in normal mode, provide host (localhost) +REDIS_HOST=redis +REDIS_PORT=6379 + +APP_PORT=8000 +KUE_PORT=5000 DEBUG=true +MAINTENANCE=false +environment=development + +SENDGRID_API_KEY= + +API_BASE_URL=http://localhost:8000 +CLIENT_BASE_URL=http://localhost:3000 -APP_SECRET=something secret -SESSION_SECRET=something secret +BASE_DIR= +CLIENT_BASE_DIR= SITE_NOREPLY_EMAIL=no-reply@jrscholarship.org START_DATE=// dd-mm-yyyy @@ -18,4 +37,4 @@ END_DATE=// dd-mm-yyyy START_DATE_JS=// mm-dd-yyyy END_DATE_JS=// mm-dd-yyyy START_YEAR=// yyyy -END_YEAR=// yyyy \ No newline at end of file +END_YEAR=// yyyy diff --git a/.gitignore b/.gitignore index 3ec544c..8d56753 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,18 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +*.out + +docker/data/* + +.vscode/ node_modules/ -.env \ No newline at end of file +.env +google_auth_credentials.json +google_auth_token.json +google_auth_service_jwt.json +mentorsList.json \ No newline at end of file diff --git a/README.md b/README.md index aa5bc45..024733d 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,36 @@ # JSPF-Server -Backend for Jitheshraj Scholarship Portal Form. \ No newline at end of file +Backend for Jitheshraj Scholarship Portal Form. + +### Prerequisites +* Install [npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) +* Install [MongoDB](https://docs.mongodb.com/manual/administration/install-community/) +* Install [redis](https://redis.io/) : + ``` + sudo apt-get install redis-server + ``` + +### Project Installation +1. Clone the repository - `git clone ` +2. Go to the project directory - `cd ` +3. Install dependencies - `npm install` +4. Copy contents of `.env.example` to a new file `.env` + * Configure `PORT`, `environment` and `SECRET` variables + * Set `DB_USERNAME`, `DB_PASSWORD` and `DB_URI` to your localhost mongodb credentials + * Get and set `SENDGRID_API_KEY` to access application mailing routes + * `API_BASE_URL = 'http://localhost:8000'` + * `CLIENT_BASE_URL = 'http://localhost:3000'` + * Set `BASE_DIR` and `CLIENT_BASE_DIR` to your appropriate directories path to configure file uploads. + * Set the `DATE` and `YEAR` variables in their respective formats. You won't be able to access certain routes without this. +5. Copy contents of `src/config/google_auth_credentials.example.json` to a new file `src/config/google_auth_credentials.json` and set all the parameters and configure accordingly in Google developer console to give Google Drive API upload/write access. +6. Copy contents of `src/config/mentorsList.example.json` to a new file `src/config/mentorsList.json` and add all the mentors details. +7. Start `MongoDB` service - `sudo service mongod start` +8. Start application and kue server - `node index` +9. Open a new terminal and start kue workers - `node /src/workers` +10. Instead, to start both servers and workers - `./run_server.sh` +11. Access kue API and dashboard from http://localhost at its configured _port_ number. Or at http://localhost:5000/api and http://localhost:5000/kue respectively, by default. +12. Install _JSPF-Web_ if needed, from [BharathKumarRavichandran/JSPF-Web](https://github.com/BharathKumarRavichandran/JSPF-Web) + +#### Troubleshooting +* If you face some version incompatability issues while installing/running, check your `node` and `npm` versions and ensure it is compatible with the project. (Tip: Use nvm :p) +* If you face any mongo error, check whether `MongoDB` service (_mongod_ daemon) is running. +* If you're unable to generate PDFs, make sure your [wkhtmltopdf](https://www.npmjs.com/package/wkhtmltopdf) rendering engine(_QT_) path is configured properly. \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..aa164d8 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,38 @@ +version: '3' + +services: + + redis: + image: redis:3.0-alpine + container_name: jspf-redis + command: redis-server + volumes: + - './docker/data/redis:/data' + ports: + - "6379:6379" + + mongodb: + image: 'mongo' + container_name: jspf-mongo + env_file: + - './docker/mongo/.env' + volumes: + - './docker/data/mongodb:/data/db' + ports: + - '27017:27017' + + node_app: + build: + context: ./ + dockerfile: ./docker/build_files/Dockerfile + container_name: jspf-server + restart: always + volumes: + - ./storage/logs:/usr/src/app/storage/logs + - ../JSPF-Web/public/uploads:/usr/src/app/public/uploads + ports: + - '5000:5000' + - '8000:8000' + depends_on: + - mongodb + - redis diff --git a/docker/build_files/Dockerfile b/docker/build_files/Dockerfile new file mode 100644 index 0000000..1d4f77d --- /dev/null +++ b/docker/build_files/Dockerfile @@ -0,0 +1,20 @@ +FROM node:8-alpine + +WORKDIR /usr/src/app + +COPY ./scripts ./scripts +RUN ./scripts/install_packages.sh + +RUN Xvfb :1 -screen 0 800x600x24& +RUN Xvfb :99 -ac & + +ENV NODE_ENV=production + +COPY ./package*.json ./ +RUN npm install --only=production +COPY . ./ + +EXPOSE 5000 +EXPOSE 8000 + +CMD ["./scripts/node_app_entry.sh"] diff --git a/docker/mongo/.env.example b/docker/mongo/.env.example new file mode 100644 index 0000000..f3f3264 --- /dev/null +++ b/docker/mongo/.env.example @@ -0,0 +1,2 @@ +MONGO_INITDB_ROOT_USERNAME=root +MONGO_INITDB_ROOT_PASSWORD=rootPassXXX \ No newline at end of file diff --git a/docker/nginx/default.conf b/docker/nginx/default.conf new file mode 100644 index 0000000..7a8129a --- /dev/null +++ b/docker/nginx/default.conf @@ -0,0 +1,32 @@ +server { + + listen 80; + server_name form.jrscholarship.org; + + access_log /var/log/nginx/access.log; + + # listen 443 ssl; + + # ssl_certificate /etc/nginx/conf.d/nginx.crt; + # ssl_certificate_key /etc/nginx/conf.d/nginx.key; + + location / { + proxy_set_header Host $host; + proxy_pass http://0.0.0.0:3000/; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + proxy_set_header Host $host; + proxy_cache_bypass $http_upgrade; + } + + location /api { + proxy_set_header Host $host; + proxy_pass http://0.0.0.0:8000/; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + proxy_set_header Host $host; + proxy_cache_bypass $http_upgrade; + } +} \ No newline at end of file diff --git a/index.js b/index.js index 5d8c959..f03c52e 100644 --- a/index.js +++ b/index.js @@ -1 +1,5 @@ -require('./src/server').start() \ No newline at end of file +// Start app server +require('./src/server.js').start(); + +// Start kue server +require('./src/kue.server.js').start(); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 7a35ee9..4038d05 100644 --- a/package-lock.json +++ b/package-lock.json @@ -436,6 +436,11 @@ "@sendgrid/helpers": "^6.4.0" } }, + "@types/babel-types": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/@types/babel-types/-/babel-types-7.0.7.tgz", + "integrity": "sha512-dBtBbrc+qTHy1WdfHYjBwRln4+LWqASWakLHsWHR2NWHIFkv4W3O070IGoGLEBrJBvct3r0L1BUPuvURi7kYUQ==" + }, "@types/babel__core": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.2.tgz", @@ -477,6 +482,14 @@ "@babel/types": "^7.3.0" } }, + "@types/babylon": { + "version": "6.16.5", + "resolved": "https://registry.npmjs.org/@types/babylon/-/babylon-6.16.5.tgz", + "integrity": "sha512-xH2e58elpj1X4ynnKp9qSnWlsRTIs6n3tgLGNfwAGHwePw0mulHQllV34n0T25uYSu1k0hRKkWXF890B1yS47w==", + "requires": { + "@types/babel-types": "*" + } + }, "@types/caseless": { "version": "0.12.2", "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.2.tgz", @@ -560,6 +573,14 @@ "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", "dev": true }, + "abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "requires": { + "event-target-shim": "^5.0.0" + } + }, "accepts": { "version": "1.3.7", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", @@ -597,6 +618,14 @@ "integrity": "sha512-OtUw6JUTgxA2QoqqmrmQ7F2NYqiBPi/L2jqHyFtllhOUvXYQXf0Z1CYUinIfyT4bTCGmrA7gX9FvHA81uzCoVw==", "dev": true }, + "agent-base": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", + "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", + "requires": { + "es6-promisify": "^5.0.0" + } + }, "ajv": { "version": "6.10.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz", @@ -608,6 +637,31 @@ "uri-js": "^4.2.2" } }, + "align-text": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", + "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", + "requires": { + "kind-of": "^3.0.2", + "longest": "^1.0.1", + "repeat-string": "^1.5.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "amdefine": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", + "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=" + }, "ansi-align": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-2.0.0.tgz", @@ -647,11 +701,60 @@ "normalize-path": "^2.1.1" } }, + "apparatus": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/apparatus/-/apparatus-0.0.10.tgz", + "integrity": "sha512-KLy/ugo33KZA7nugtQ7O0E1c8kQ52N3IvD/XgIh4w/Nr28ypfkwDfA67F1ev4N1m5D+BOk1+b2dEJDfpj/VvZg==", + "optional": true, + "requires": { + "sylvester": ">= 0.0.8" + } + }, "append-field": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", "integrity": "sha1-HjRA6RXwsSA9I3SOeO3XubW0PlY=" }, + "archiver": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-3.0.0.tgz", + "integrity": "sha512-5QeR6Xc5hSA9X1rbQfcuQ6VZuUXOaEdB65Dhmk9duuRJHYif/ZyJfuyJqsQrj34PFjU5emv5/MmfgA8un06onw==", + "requires": { + "archiver-utils": "^2.0.0", + "async": "^2.0.0", + "buffer-crc32": "^0.2.1", + "glob": "^7.0.0", + "readable-stream": "^2.0.0", + "tar-stream": "^1.5.0", + "zip-stream": "^2.0.1" + } + }, + "archiver-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.0.0.tgz", + "integrity": "sha512-JRBgcVvDX4Mwu2RBF8bBaHcQCSxab7afsxAPYDQ5W+19quIPP5CfKE7Ql+UHs9wYvwsaNR8oDuhtf5iqrKmzww==", + "requires": { + "glob": "^7.0.0", + "graceful-fs": "^4.1.0", + "lazystream": "^1.0.0", + "lodash.assign": "^4.2.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.toarray": "^4.4.0", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^2.0.0" + }, + "dependencies": { + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" + } + } + }, "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -701,6 +804,11 @@ "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", "dev": true }, + "asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" + }, "asn1": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", @@ -838,11 +946,42 @@ "babel-plugin-jest-hoist": "^24.6.0" } }, + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + }, + "babel-types": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", + "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", + "requires": { + "babel-runtime": "^6.26.0", + "esutils": "^2.0.2", + "lodash": "^4.17.4", + "to-fast-properties": "^1.0.3" + }, + "dependencies": { + "to-fast-properties": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", + "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=" + } + } + }, + "babylon": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", + "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==" + }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, "base": { "version": "0.11.2", @@ -899,6 +1038,31 @@ } } }, + "base64-js": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz", + "integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==" + }, + "base64-url": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/base64-url/-/base64-url-1.2.1.tgz", + "integrity": "sha1-GZ/WYXAqDnt9yubgaYuwicUvbXg=" + }, + "basic-auth": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-1.0.4.tgz", + "integrity": "sha1-Awk1sB3nyblKgksp8/zLdQ06UpA=" + }, + "basic-auth-connect": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/basic-auth-connect/-/basic-auth-connect-1.0.0.tgz", + "integrity": "sha1-/bC0OWLKe0BFanwrtI/hc9otISI=" + }, + "batch": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.5.3.tgz", + "integrity": "sha1-PzQU84AyF0O/wQQvmoP/HVgk1GQ=" + }, "bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", @@ -912,12 +1076,26 @@ "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", "integrity": "sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms=" }, + "bignumber.js": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-7.2.1.tgz", + "integrity": "sha512-S4XzBk5sMB+Rcb/LNcpzXr57VRTxgAvaAEDAl1AwRx27j00hT84O6OkteE7u8UB3NuaaygCRrEpqox4uDOrbdQ==" + }, "binary-extensions": { "version": "1.13.1", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", "dev": true }, + "bl": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz", + "integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==", + "requires": { + "readable-stream": "^2.3.5", + "safe-buffer": "^5.1.1" + } + }, "bluebird": { "version": "3.5.1", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", @@ -967,7 +1145,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -1039,6 +1216,44 @@ "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.1.tgz", "integrity": "sha512-jCGVYLoYMHDkOsbwJZBCqwMHyH4c+wzgI9hG7Z6SZJRXWr+x58pdIbm2i9a/jFGCkRJqRUr8eoI7lDWa0hTkxg==" }, + "buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.2.1.tgz", + "integrity": "sha512-c+Ko0loDaFfuPWiL02ls9Xd3GO3cPVmUobQ6t3rXNUk304u6hGq+8N/kFi+QEIKhzK3uwolVhLzszmfLmMLnqg==", + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4" + } + }, + "buffer-alloc": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", + "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", + "requires": { + "buffer-alloc-unsafe": "^1.1.0", + "buffer-fill": "^1.0.0" + } + }, + "buffer-alloc-unsafe": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", + "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==" + }, + "buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=" + }, + "buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" + }, + "buffer-fill": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", + "integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw=" + }, "buffer-from": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", @@ -1130,6 +1345,15 @@ "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" }, + "center-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", + "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", + "requires": { + "align-text": "^0.1.3", + "lazy-cache": "^1.0.3" + } + }, "chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -1140,6 +1364,14 @@ "supports-color": "^5.3.0" } }, + "character-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/character-parser/-/character-parser-2.2.0.tgz", + "integrity": "sha1-x84o821LzZdE5f/CxfzeHHMmH8A=", + "requires": { + "is-regex": "^1.0.3" + } + }, "chardet": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", @@ -1203,6 +1435,14 @@ } } }, + "clean-css": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.1.tgz", + "integrity": "sha512-4ZxI6dy4lrY6FHzfiy1aEOXgu4LIsW2MhwG0VBKdcoGoH/XLFgaHSdLTGr4O8Be6A8r3MOphEiI8Gc1n0ecf3g==", + "requires": { + "source-map": "~0.6.0" + } + }, "cli-boxes": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-1.0.0.tgz", @@ -1244,8 +1484,7 @@ "code-point-at": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "dev": true + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" }, "collection-visit": { "version": "1.0.0", @@ -1257,6 +1496,15 @@ "object-visit": "^1.0.0" } }, + "color": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/color/-/color-3.0.0.tgz", + "integrity": "sha512-jCpd5+s0s0t7p3pHQKpnJ0TpQKKdleP71LWcA0aqiljpiuAkOSUFN/dyH8ZwF0hRmFlrIuRhufds1QyEP9EB+w==", + "requires": { + "color-convert": "^1.9.1", + "color-string": "^1.5.2" + } + }, "color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -1270,6 +1518,34 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" }, + "color-string": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.3.tgz", + "integrity": "sha512-dC2C5qeWoYkxki5UAXapdjqO672AM4vZuPGRQfO8b5HKuKGBbKWpITyDYN7TOFKvRW7kOgAn3746clDBMDJyQw==", + "requires": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "colornames": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/colornames/-/colornames-1.1.1.tgz", + "integrity": "sha1-+IiQMGhcfE/54qVZ9Qd+t2qBb5Y=" + }, + "colors": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.3.3.tgz", + "integrity": "sha512-mmGt/1pZqYRjMxB1axhTo16/snVZ5krrKkcmMeVKxzECMMXoCgnvTPp10QgHfcbQZw8Dq2jMNG6je4JlWU0gWg==" + }, + "colorspace": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.2.tgz", + "integrity": "sha512-vt+OoIP2d76xLhjwbBaucYlNSpPsrJWPlBTtwCpQKIu6/CSMutyzX93O/Do0qzpH3YoHEes8YEFXyZ797rEhzQ==", + "requires": { + "color": "3.0.x", + "text-hex": "1.0.x" + } + }, "combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -1291,11 +1567,81 @@ "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", "dev": true }, + "compress-commons": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-1.2.2.tgz", + "integrity": "sha1-UkqfEJA/OoEzibAiXSfEi7dRiQ8=", + "requires": { + "buffer-crc32": "^0.2.1", + "crc32-stream": "^2.0.0", + "normalize-path": "^2.0.0", + "readable-stream": "^2.0.0" + } + }, + "compressible": { + "version": "2.0.17", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.17.tgz", + "integrity": "sha512-BGHeLCK1GV7j1bSmQQAi26X+GgWcTjLr/0tzSvMCl3LH1w1IJ4PFSPoV5316b30cneTziC+B1a+3OjoSUcQYmw==", + "requires": { + "mime-db": ">= 1.40.0 < 2" + } + }, + "compression": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.5.2.tgz", + "integrity": "sha1-sDuNhub4rSloPLqN+R3cb/x3s5U=", + "requires": { + "accepts": "~1.2.12", + "bytes": "2.1.0", + "compressible": "~2.0.5", + "debug": "~2.2.0", + "on-headers": "~1.0.0", + "vary": "~1.0.1" + }, + "dependencies": { + "accepts": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.2.13.tgz", + "integrity": "sha1-5fHzkoxtlf2WVYw27D2dDeSm7Oo=", + "requires": { + "mime-types": "~2.1.6", + "negotiator": "0.5.3" + } + }, + "bytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-2.1.0.tgz", + "integrity": "sha1-rJPEEOL/ycx89LRks4KJBn9eR7Q=" + }, + "debug": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", + "requires": { + "ms": "0.7.1" + } + }, + "ms": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", + "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=" + }, + "negotiator": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.5.3.tgz", + "integrity": "sha1-Jp1cR2gQ7JLtvntsLygxY4T5p+g=" + }, + "vary": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.0.1.tgz", + "integrity": "sha1-meSYFWaihhGN+yuBc1ffeZM3bRA=" + } + } + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, "concat-stream": { "version": "1.6.2", @@ -1357,6 +1703,330 @@ } } }, + "connect": { + "version": "2.30.2", + "resolved": "https://registry.npmjs.org/connect/-/connect-2.30.2.tgz", + "integrity": "sha1-jam8vooFTT0xjXTf7JA7XDmhtgk=", + "requires": { + "basic-auth-connect": "1.0.0", + "body-parser": "~1.13.3", + "bytes": "2.1.0", + "compression": "~1.5.2", + "connect-timeout": "~1.6.2", + "content-type": "~1.0.1", + "cookie": "0.1.3", + "cookie-parser": "~1.3.5", + "cookie-signature": "1.0.6", + "csurf": "~1.8.3", + "debug": "~2.2.0", + "depd": "~1.0.1", + "errorhandler": "~1.4.2", + "express-session": "~1.11.3", + "finalhandler": "0.4.0", + "fresh": "0.3.0", + "http-errors": "~1.3.1", + "method-override": "~2.3.5", + "morgan": "~1.6.1", + "multiparty": "3.3.2", + "on-headers": "~1.0.0", + "parseurl": "~1.3.0", + "pause": "0.1.0", + "qs": "4.0.0", + "response-time": "~2.3.1", + "serve-favicon": "~2.3.0", + "serve-index": "~1.7.2", + "serve-static": "~1.10.0", + "type-is": "~1.6.6", + "utils-merge": "1.0.0", + "vhost": "~3.0.1" + }, + "dependencies": { + "body-parser": { + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.13.3.tgz", + "integrity": "sha1-wIzzMMM1jhUQFqBXRvE/ApyX+pc=", + "requires": { + "bytes": "2.1.0", + "content-type": "~1.0.1", + "debug": "~2.2.0", + "depd": "~1.0.1", + "http-errors": "~1.3.1", + "iconv-lite": "0.4.11", + "on-finished": "~2.3.0", + "qs": "4.0.0", + "raw-body": "~2.1.2", + "type-is": "~1.6.6" + } + }, + "bytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-2.1.0.tgz", + "integrity": "sha1-rJPEEOL/ycx89LRks4KJBn9eR7Q=" + }, + "cookie": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.1.3.tgz", + "integrity": "sha1-5zSlwUF/zkctWu+Cw4HKu2TRpDU=" + }, + "cookie-parser": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.3.5.tgz", + "integrity": "sha1-nXVVcPtdF4kHcSJ6AjFNm+fPg1Y=", + "requires": { + "cookie": "0.1.3", + "cookie-signature": "1.0.6" + } + }, + "crc": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/crc/-/crc-3.3.0.tgz", + "integrity": "sha1-+mIuG8OIvyVzCQgta2UgDOZwkLo=" + }, + "debug": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", + "requires": { + "ms": "0.7.1" + } + }, + "depd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.0.1.tgz", + "integrity": "sha1-gK7GTJ1tl+ZcwqnKqTwKpqv3Oqo=" + }, + "escape-html": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.2.tgz", + "integrity": "sha1-130y+pjjjC9BroXpJ44ODmuhAiw=" + }, + "etag": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.7.0.tgz", + "integrity": "sha1-A9MLX2fdbmMtKUXTDWZScxo01dg=" + }, + "express-session": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.11.3.tgz", + "integrity": "sha1-XMmPP1/4Ttg1+Ry/CqvQxxB0AK8=", + "requires": { + "cookie": "0.1.3", + "cookie-signature": "1.0.6", + "crc": "3.3.0", + "debug": "~2.2.0", + "depd": "~1.0.1", + "on-headers": "~1.0.0", + "parseurl": "~1.3.0", + "uid-safe": "~2.0.0", + "utils-merge": "1.0.0" + } + }, + "finalhandler": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-0.4.0.tgz", + "integrity": "sha1-llpS2ejQXSuFdUhUH7ibU6JJfZs=", + "requires": { + "debug": "~2.2.0", + "escape-html": "1.0.2", + "on-finished": "~2.3.0", + "unpipe": "~1.0.0" + } + }, + "fresh": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.3.0.tgz", + "integrity": "sha1-ZR+DjiJCTnVm3hYdg1jKoZn4PU8=" + }, + "http-errors": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.3.1.tgz", + "integrity": "sha1-GX4izevUGYWF6GlO9nhhl7ke2UI=", + "requires": { + "inherits": "~2.0.1", + "statuses": "1" + } + }, + "iconv-lite": { + "version": "0.4.11", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.11.tgz", + "integrity": "sha1-LstC/SlHRJIiCaLnxATayHk9it4=" + }, + "mime": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.3.4.tgz", + "integrity": "sha1-EV+eO2s9rylZmDyzjxSaLUDrXVM=" + }, + "morgan": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.6.1.tgz", + "integrity": "sha1-X9gYOYxoGcuiinzWZk8pL+HAu/I=", + "requires": { + "basic-auth": "~1.0.3", + "debug": "~2.2.0", + "depd": "~1.0.1", + "on-finished": "~2.3.0", + "on-headers": "~1.0.0" + } + }, + "ms": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", + "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=" + }, + "qs": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-4.0.0.tgz", + "integrity": "sha1-wx2bdOwn33XlQ6hseHKO2NRiNgc=" + }, + "range-parser": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.0.3.tgz", + "integrity": "sha1-aHKCNTXGkuLCoBA4Jq/YLC4P8XU=" + }, + "raw-body": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.1.7.tgz", + "integrity": "sha1-rf6s4uT7MJgFgBTQjActzFl1h3Q=", + "requires": { + "bytes": "2.4.0", + "iconv-lite": "0.4.13", + "unpipe": "1.0.0" + }, + "dependencies": { + "bytes": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-2.4.0.tgz", + "integrity": "sha1-fZcZb51br39pNeJZhVSe3SpsIzk=" + }, + "iconv-lite": { + "version": "0.4.13", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.13.tgz", + "integrity": "sha1-H4irpKsLFQjoMSrMOTRfNumS4vI=" + } + } + }, + "send": { + "version": "0.13.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.13.2.tgz", + "integrity": "sha1-dl52B8gFVFK7pvCwUllTUJhgNt4=", + "requires": { + "debug": "~2.2.0", + "depd": "~1.1.0", + "destroy": "~1.0.4", + "escape-html": "~1.0.3", + "etag": "~1.7.0", + "fresh": "0.3.0", + "http-errors": "~1.3.1", + "mime": "1.3.4", + "ms": "0.7.1", + "on-finished": "~2.3.0", + "range-parser": "~1.0.3", + "statuses": "~1.2.1" + }, + "dependencies": { + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, + "statuses": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.2.1.tgz", + "integrity": "sha1-3e1FzBglbVHtQK7BQkidXGECbSg=" + } + } + }, + "serve-static": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.10.3.tgz", + "integrity": "sha1-zlpuzTEB/tXsCYJ9rCKpwpv7BTU=", + "requires": { + "escape-html": "~1.0.3", + "parseurl": "~1.3.1", + "send": "0.13.2" + }, + "dependencies": { + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + } + } + }, + "uid-safe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.0.0.tgz", + "integrity": "sha1-p/PGymSh9qXQTsDvPkw9U2cxcTc=", + "requires": { + "base64-url": "1.2.1" + } + }, + "utils-merge": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.0.tgz", + "integrity": "sha1-ApT7kiu5N1FTVBxPcJYjHyh8ivg=" + } + } + }, + "connect-mongo": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/connect-mongo/-/connect-mongo-3.0.0.tgz", + "integrity": "sha512-Y95urWNGrAoKY2w31s7Q9Gs/W3qdMCshUIeDTgulssHi6KueYtz4XrbV3kcnQaR8EcBQvooNNX7aOaAJDgudag==", + "requires": { + "mongodb": "^3.1.0" + } + }, + "connect-timeout": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/connect-timeout/-/connect-timeout-1.6.2.tgz", + "integrity": "sha1-3ppexh4zoStu2qt7XwYumMWZuI4=", + "requires": { + "debug": "~2.2.0", + "http-errors": "~1.3.1", + "ms": "0.7.1", + "on-headers": "~1.0.0" + }, + "dependencies": { + "debug": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", + "requires": { + "ms": "0.7.1" + } + }, + "http-errors": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.3.1.tgz", + "integrity": "sha1-GX4izevUGYWF6GlO9nhhl7ke2UI=", + "requires": { + "inherits": "~2.0.1", + "statuses": "1" + } + }, + "ms": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", + "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=" + } + } + }, + "constantinople": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/constantinople/-/constantinople-3.1.2.tgz", + "integrity": "sha512-yePcBqEFhLOqSBtwYOGGS1exHo/s1xjekXiinh4itpNQGCu4KA1euPh1fg07N2wMITZXQkBz75Ntdt1ctGZouw==", + "requires": { + "@types/babel-types": "^7.0.0", + "@types/babylon": "^6.16.2", + "babel-types": "^6.26.0", + "babylon": "^6.18.0" + } + }, "content-disposition": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", @@ -1411,6 +2081,11 @@ "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", "dev": true }, + "core-js": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.9.tgz", + "integrity": "sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A==" + }, "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", @@ -1425,6 +2100,23 @@ "vary": "^1" } }, + "crc": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/crc/-/crc-3.8.0.tgz", + "integrity": "sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==", + "requires": { + "buffer": "^5.1.0" + } + }, + "crc32-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-2.0.0.tgz", + "integrity": "sha1-483TtN8xaN10494/u8t7KX/pCPQ=", + "requires": { + "crc": "^3.4.4", + "readable-stream": "^2.0.0" + } + }, "create-error-class": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/create-error-class/-/create-error-class-3.0.2.tgz", @@ -1453,6 +2145,52 @@ "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=", "dev": true }, + "csrf": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/csrf/-/csrf-3.0.6.tgz", + "integrity": "sha1-thEg3c7q/JHnbtUxO7XAsmZ7cQo=", + "requires": { + "rndm": "1.2.0", + "tsscmp": "1.0.5", + "uid-safe": "2.1.4" + }, + "dependencies": { + "uid-safe": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.4.tgz", + "integrity": "sha1-Otbzg2jG1MjHXsF2I/t5qh0HHYE=", + "requires": { + "random-bytes": "~1.0.0" + } + } + } + }, + "css": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/css/-/css-1.0.8.tgz", + "integrity": "sha1-k4aBHKgrzMnuf7WnMrHioxfIo+c=", + "requires": { + "css-parse": "1.0.4", + "css-stringify": "1.0.5" + }, + "dependencies": { + "css-parse": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/css-parse/-/css-parse-1.0.4.tgz", + "integrity": "sha1-OLBQP7+dqfVOnB29pg4UXHcRe90=" + } + } + }, + "css-parse": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/css-parse/-/css-parse-1.7.0.tgz", + "integrity": "sha1-Mh9s9zeCpv91ERE5D8BeLGV9jJs=" + }, + "css-stringify": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/css-stringify/-/css-stringify-1.0.5.tgz", + "integrity": "sha1-sNBClG2ylTu50pKQCmy19tASIDE=" + }, "cssom": { "version": "0.3.6", "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.6.tgz", @@ -1468,6 +2206,33 @@ "cssom": "0.3.x" } }, + "csurf": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/csurf/-/csurf-1.8.3.tgz", + "integrity": "sha1-I/KhO/HY/OHQyZZYg5RELLqGpWo=", + "requires": { + "cookie": "0.1.3", + "cookie-signature": "1.0.6", + "csrf": "~3.0.0", + "http-errors": "~1.3.1" + }, + "dependencies": { + "cookie": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.1.3.tgz", + "integrity": "sha1-5zSlwUF/zkctWu+Cw4HKu2TRpDU=" + }, + "http-errors": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.3.1.tgz", + "integrity": "sha1-GX4izevUGYWF6GlO9nhhl7ke2UI=", + "requires": { + "inherits": "~2.0.1", + "statuses": "1" + } + } + } + }, "dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", @@ -1511,8 +2276,7 @@ "decamelize": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "dev": true + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" }, "decode-uri-component": { "version": "0.2.0", @@ -1541,7 +2305,6 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "dev": true, "requires": { "object-keys": "^1.0.12" } @@ -1587,6 +2350,11 @@ } } }, + "delay": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/delay/-/delay-4.3.0.tgz", + "integrity": "sha512-Lwaf3zVFDMBop1yDuFZ19F9WyGcZcGacsbdlZtWjQmM50tOcMntm1njF/Nb/Vjij3KaSvCF+sEYGKrrjObu2NA==" + }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -1608,6 +2376,16 @@ "integrity": "sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=", "dev": true }, + "diagnostics": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/diagnostics/-/diagnostics-1.1.1.tgz", + "integrity": "sha512-8wn1PmdunLJ9Tqbx+Fx/ZEuHfJf4NKSN2ZBj7SJC/OWRWha843+WsTjqMe1B5E3p28jqBlp+mJ2fPVxPyNgYKQ==", + "requires": { + "colorspace": "1.1.x", + "enabled": "1.0.x", + "kuler": "1.0.x" + } + }, "dicer": { "version": "0.2.5", "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.2.5.tgz", @@ -1655,6 +2433,11 @@ "esutils": "^2.0.2" } }, + "doctypes": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/doctypes/-/doctypes-1.1.0.tgz", + "integrity": "sha1-6oCxBqh1OHdOijpKWv4pPeSJ4Kk=" + }, "domexception": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz", @@ -1678,12 +2461,22 @@ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.0.0.tgz", "integrity": "sha512-30xVGqjLjiUOArT4+M5q9sYdvuR4riM6yK9wMcas9Vbp6zZa+ocC9dp6QoftuhTPhFAiLK/0C5Ni2nou/Bk8lg==" }, + "double-ended-queue": { + "version": "2.1.0-0", + "resolved": "https://registry.npmjs.org/double-ended-queue/-/double-ended-queue-2.1.0-0.tgz", + "integrity": "sha1-ED01J/0xUo9AGIEwyEHv3XgmTlw=" + }, "duplexer3": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", "dev": true }, + "easy-pdf-merge": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/easy-pdf-merge/-/easy-pdf-merge-0.2.1.tgz", + "integrity": "sha512-1r0rlM9XpUQ+1k+GiayLGfhM1WP4gLXDlWJM2thkokWRz1nHGZzwDWUMpNWZoVhCtWUEedDVsOVa4iDedcpw2Q==" + }, "ecc-jsbn": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", @@ -1693,17 +2486,38 @@ "safer-buffer": "^2.1.0" } }, + "ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "requires": { + "safe-buffer": "^5.0.1" + } + }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, + "ejs": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.6.2.tgz", + "integrity": "sha512-PcW2a0tyTuPHz3tWyYqtK6r1fZ3gp+3Sop8Ph+ZYN81Ob5rwmbHEzaqs10N3BEsaGTkh/ooniXK+WwszGlc2+Q==" + }, "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 }, + "enabled": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-1.0.2.tgz", + "integrity": "sha1-ll9lE9LC0cX0ZStkouM5ZGf8L5M=", + "requires": { + "env-variable": "0.0.x" + } + }, "encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", @@ -1713,11 +2527,15 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", - "dev": true, "requires": { "once": "^1.4.0" } }, + "env-variable": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/env-variable/-/env-variable-0.0.5.tgz", + "integrity": "sha512-zoB603vQReOFvTg5xMl9I1P2PnHsHQQKTEowsKKD7nseUfJq6UWzK+4YtlWUO1nhiQUxe6XMkk+JleSZD1NZFA==" + }, "error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -1726,11 +2544,19 @@ "is-arrayish": "^0.2.1" } }, + "errorhandler": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/errorhandler/-/errorhandler-1.4.3.tgz", + "integrity": "sha1-t7cO2PNZ6duICS8tIMD4MUIK2D8=", + "requires": { + "accepts": "~1.3.0", + "escape-html": "~1.0.3" + } + }, "es-abstract": { "version": "1.13.0", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.13.0.tgz", "integrity": "sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg==", - "dev": true, "requires": { "es-to-primitive": "^1.2.0", "function-bind": "^1.1.1", @@ -1744,13 +2570,25 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz", "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==", - "dev": true, "requires": { "is-callable": "^1.1.4", "is-date-object": "^1.0.1", "is-symbol": "^1.0.2" } }, + "es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" + }, + "es6-promisify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", + "requires": { + "es6-promise": "^4.0.3" + } + }, "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -1882,10 +2720,13 @@ } }, "eslint-utils": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.3.1.tgz", - "integrity": "sha512-Z7YjnIldX+2XMcjr7ZkgEsOj/bREONV60qYeB/bjMAqqqZ4zxKyWX+BOUkdmRmA9riiIPVvo5x86m5elviOk0Q==", - "dev": true + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.2.tgz", + "integrity": "sha512-eAZS2sEUMlIeCjBeubdj45dmBHQwPHWyBcT1VSYB7o9x9WRRqKxyUoiXlRjyAwzN7YEzHJlYg0NmzDRWx6GP4Q==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.0.0" + } }, "eslint-visitor-keys": { "version": "1.0.0", @@ -1937,14 +2778,18 @@ "esutils": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", - "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", - "dev": true + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=" }, "etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" }, + "event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==" + }, "exec-sh": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.3.2.tgz", @@ -2224,6 +3069,16 @@ "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==" + }, + "fast-text-encoding": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.0.tgz", + "integrity": "sha512-R9bHCvweUxxwkDwhjav5vxpFvdPGlVngtqmx4pIZfSUhM/Q4NiIUHB456BAf+Q1Nwu3HEZYONtu+Rya+af4jiQ==" + }, "fb-watchman": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.0.tgz", @@ -2233,6 +3088,11 @@ "bser": "^2.0.0" } }, + "fecha": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-2.3.3.tgz", + "integrity": "sha512-lUGBnIamTAwk4znq5BcqsDaxSmZ9nDVJaij6NvRt/Tg4R69gERA+otPKbS86ROw9nxVMw2/mp1fnaiWqbs6Sdg==" + }, "figures": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", @@ -2353,11 +3213,15 @@ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" }, + "fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" + }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "fsevents": { "version": "1.2.9", @@ -2910,8 +3774,7 @@ "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, "functional-red-black-tree": { "version": "1.0.1", @@ -2919,11 +3782,30 @@ "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", "dev": true }, + "gaxios": { + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-1.8.4.tgz", + "integrity": "sha512-BoENMnu1Gav18HcpV9IleMPZ9exM+AvUjrAOV4Mzs/vfz2Lu/ABv451iEXByKiMPn2M140uul1txXCg83sAENw==", + "requires": { + "abort-controller": "^3.0.0", + "extend": "^3.0.2", + "https-proxy-agent": "^2.2.1", + "node-fetch": "^2.3.0" + } + }, + "gcp-metadata": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-1.0.0.tgz", + "integrity": "sha512-Q6HrgfrCQeEircnNP3rCcEgiDv7eF9+1B+1MMgpE190+/+0mjQR8PxeOaRgxZWmdDAF9EIryHB9g1moPiw1SbQ==", + "requires": { + "gaxios": "^1.0.2", + "json-bigint": "^0.3.0" + } + }, "get-caller-file": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", - "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", - "dev": true + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==" }, "get-stream": { "version": "4.1.0", @@ -2952,7 +3834,6 @@ "version": "7.1.4", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", - "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -2998,6 +3879,82 @@ "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", "dev": true }, + "google-auth-library": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-3.1.2.tgz", + "integrity": "sha512-cDQMzTotwyWMrg5jRO7q0A4TL/3GWBgO7I7q5xGKNiiFf9SmGY/OJ1YsLMgI2MVHHsEGyrqYnbnmV1AE+Z6DnQ==", + "requires": { + "base64-js": "^1.3.0", + "fast-text-encoding": "^1.0.0", + "gaxios": "^1.2.1", + "gcp-metadata": "^1.0.0", + "gtoken": "^2.3.2", + "https-proxy-agent": "^2.2.1", + "jws": "^3.1.5", + "lru-cache": "^5.0.0", + "semver": "^5.5.0" + }, + "dependencies": { + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "requires": { + "yallist": "^3.0.2" + } + }, + "yallist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", + "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==" + } + } + }, + "google-p12-pem": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-1.0.4.tgz", + "integrity": "sha512-SwLAUJqUfTB2iS+wFfSS/G9p7bt4eWcc2LyfvmUXe7cWp6p3mpxDo6LLI29MXdU6wvPcQ/up298X7GMC5ylAlA==", + "requires": { + "node-forge": "^0.8.0", + "pify": "^4.0.0" + }, + "dependencies": { + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==" + } + } + }, + "googleapis": { + "version": "39.2.0", + "resolved": "https://registry.npmjs.org/googleapis/-/googleapis-39.2.0.tgz", + "integrity": "sha512-66X8TG1B33zAt177sG1CoKoYHPP/B66tEpnnSANGCqotMuY5gqSQO8G/0gqHZR2jRgc5CHSSNOJCnpI0SuDxMQ==", + "requires": { + "google-auth-library": "^3.0.0", + "googleapis-common": "^0.7.0" + } + }, + "googleapis-common": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/googleapis-common/-/googleapis-common-0.7.2.tgz", + "integrity": "sha512-9DEJIiO4nS7nw0VE1YVkEfXEj8x8MxsuB+yZIpOBULFSN9OIKcUU8UuKgSZFU4lJmRioMfngktrbkMwWJcUhQg==", + "requires": { + "gaxios": "^1.2.2", + "google-auth-library": "^3.0.0", + "pify": "^4.0.0", + "qs": "^6.5.2", + "url-template": "^2.0.8", + "uuid": "^3.2.1" + }, + "dependencies": { + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==" + } + } + }, "got": { "version": "6.7.1", "resolved": "https://registry.npmjs.org/got/-/got-6.7.1.tgz", @@ -3030,12 +3987,41 @@ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==" }, + "graceful-readlink": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", + "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=" + }, "growly": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=", "dev": true }, + "gtoken": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-2.3.3.tgz", + "integrity": "sha512-EaB49bu/TCoNeQjhCYKI/CurooBKkGxIqFHsWABW0b25fobBYVTMe84A8EBVVZhl8emiUdNypil9huMOTmyAnw==", + "requires": { + "gaxios": "^1.0.4", + "google-p12-pem": "^1.0.0", + "jws": "^3.1.5", + "mime": "^2.2.0", + "pify": "^4.0.0" + }, + "dependencies": { + "mime": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz", + "integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==" + }, + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==" + } + } + }, "handlebars": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.1.2.tgz", @@ -3066,7 +4052,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, "requires": { "function-bind": "^1.1.1" } @@ -3079,8 +4064,7 @@ "has-symbols": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", - "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", - "dev": true + "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=" }, "has-value": { "version": "1.0.0", @@ -3117,8 +4101,7 @@ "hosted-git-info": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", - "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==", - "dev": true + "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==" }, "html-encoding-sniffer": { "version": "1.0.2", @@ -3151,6 +4134,35 @@ "sshpk": "^1.7.0" } }, + "http-status-codes": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/http-status-codes/-/http-status-codes-1.3.2.tgz", + "integrity": "sha512-nDUtj0ltIt08tGi2VWSpSzNNFye0v3YSe9lX3lIqLTuVvvRiYCvs4QQBSHo0eomFYw1wlUuofurUAlTm+vHnXg==" + }, + "https-proxy-agent": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz", + "integrity": "sha512-HPCTS1LW51bcyMYbxUIOO4HEOlQ1/1qRaFWcyxvwaqUS9TY88aoEuHUY33kuAh1YhVVaDQhLZsnPd+XNARWZlQ==", + "requires": { + "agent-base": "^4.1.0", + "debug": "^3.1.0" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, "iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -3159,6 +4171,11 @@ "safer-buffer": ">= 2.1.2 < 3" } }, + "ieee754": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" + }, "ignore": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", @@ -3215,7 +4232,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, "requires": { "once": "^1.3.0", "wrappy": "1" @@ -3310,6 +4326,11 @@ } } }, + "is-arguments": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.0.4.tgz", + "integrity": "sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==" + }, "is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", @@ -3327,14 +4348,12 @@ "is-buffer": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" }, "is-callable": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", - "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", - "dev": true + "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==" }, "is-ci": { "version": "2.0.0", @@ -3368,8 +4387,7 @@ "is-date-object": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", - "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", - "dev": true + "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=" }, "is-descriptor": { "version": "0.1.6", @@ -3390,6 +4408,22 @@ } } }, + "is-expression": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-expression/-/is-expression-3.0.0.tgz", + "integrity": "sha1-Oayqa+f9HzRx3ELHQW5hwkMXrJ8=", + "requires": { + "acorn": "~4.0.2", + "object-assign": "^4.0.1" + }, + "dependencies": { + "acorn": { + "version": "4.0.13", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz", + "integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c=" + } + } + }, "is-extendable": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", @@ -3414,6 +4448,11 @@ "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", "dev": true }, + "is-generator-function": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.7.tgz", + "integrity": "sha512-YZc5EwyO4f2kWCax7oegfuSr9mFz1ZvieNYBEjmukLxgXfBUbxAWGVF7GZf0zidYtoBl3WvC07YK0wT76a+Rtw==" + }, "is-glob": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", @@ -3486,8 +4525,7 @@ "is-promise": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", - "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", - "dev": true + "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=" }, "is-redirect": { "version": "1.0.0", @@ -3499,7 +4537,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", - "dev": true, "requires": { "has": "^1.0.1" } @@ -3513,14 +4550,12 @@ "is-stream": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", - "dev": true + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" }, "is-symbol": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", - "dev": true, "requires": { "has-symbols": "^1.0.0" } @@ -3530,6 +4565,11 @@ "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" }, + "is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=" + }, "is-windows": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", @@ -3654,6 +4694,169 @@ "handlebars": "^4.1.2" } }, + "jade": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/jade/-/jade-1.11.0.tgz", + "integrity": "sha1-nIDlOMEtP7lcjZu5VZ+gzAQEBf0=", + "requires": { + "character-parser": "1.2.1", + "clean-css": "^3.1.9", + "commander": "~2.6.0", + "constantinople": "~3.0.1", + "jstransformer": "0.0.2", + "mkdirp": "~0.5.0", + "transformers": "2.1.0", + "uglify-js": "^2.4.19", + "void-elements": "~2.0.1", + "with": "~4.0.0" + }, + "dependencies": { + "acorn": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-2.7.0.tgz", + "integrity": "sha1-q259nYhqrKiwhbwzEreaGYQz8Oc=" + }, + "acorn-globals": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-1.0.9.tgz", + "integrity": "sha1-VbtemGkVB7dFedBRNBMhfDgMVM8=", + "requires": { + "acorn": "^2.1.0" + } + }, + "asap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/asap/-/asap-1.0.0.tgz", + "integrity": "sha1-sqRdpf36ILBJb8N2jMJ8EvqRan0=" + }, + "camelcase": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", + "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=" + }, + "character-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/character-parser/-/character-parser-1.2.1.tgz", + "integrity": "sha1-wN3kqxgnE7kZuXCVmhI+zBow/NY=" + }, + "clean-css": { + "version": "3.4.28", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-3.4.28.tgz", + "integrity": "sha1-vxlF6C/ICPVWlebd6uwBQA79A/8=", + "requires": { + "commander": "2.8.x", + "source-map": "0.4.x" + }, + "dependencies": { + "commander": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.8.1.tgz", + "integrity": "sha1-Br42f+v9oMMwqh4qBy09yXYkJdQ=", + "requires": { + "graceful-readlink": ">= 1.0.0" + } + } + } + }, + "cliui": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", + "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", + "requires": { + "center-align": "^0.1.1", + "right-align": "^0.1.1", + "wordwrap": "0.0.2" + } + }, + "commander": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.6.0.tgz", + "integrity": "sha1-nfflL7Kgyw+4kFjugMMQQiXzfh0=" + }, + "constantinople": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/constantinople/-/constantinople-3.0.2.tgz", + "integrity": "sha1-S5RdmTeQe82Y7ldRIsOBdRZUQUE=", + "requires": { + "acorn": "^2.1.0" + } + }, + "jstransformer": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/jstransformer/-/jstransformer-0.0.2.tgz", + "integrity": "sha1-eq4pqQPRls+glz2IXT5HlH7Ndqs=", + "requires": { + "is-promise": "^2.0.0", + "promise": "^6.0.1" + } + }, + "promise": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/promise/-/promise-6.1.0.tgz", + "integrity": "sha1-LOcp9rlLRcJoka0GAsXJDgTG7vY=", + "requires": { + "asap": "~1.0.0" + } + }, + "source-map": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", + "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", + "requires": { + "amdefine": ">=0.0.4" + } + }, + "uglify-js": { + "version": "2.8.29", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", + "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", + "requires": { + "source-map": "~0.5.1", + "uglify-to-browserify": "~1.0.0", + "yargs": "~3.10.0" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + } + } + }, + "with": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/with/-/with-4.0.3.tgz", + "integrity": "sha1-7v0VTp550sjTQXtkeo8U2f7M4U4=", + "requires": { + "acorn": "^1.0.1", + "acorn-globals": "^1.0.3" + }, + "dependencies": { + "acorn": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-1.2.2.tgz", + "integrity": "sha1-yM4n3grMdtiW0rH6099YjZ6C8BQ=" + } + } + }, + "wordwrap": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", + "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=" + }, + "yargs": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", + "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", + "requires": { + "camelcase": "^1.0.2", + "cliui": "^2.1.0", + "decamelize": "^1.0.0", + "window-size": "0.1.0" + } + } + } + }, "jest": { "version": "24.8.0", "resolved": "https://registry.npmjs.org/jest/-/jest-24.8.0.tgz", @@ -4070,6 +5273,11 @@ } } }, + "js-stringify": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/js-stringify/-/js-stringify-1.0.2.tgz", + "integrity": "sha1-Fzb939lyTyijaCrcYjCufk6Weds=" + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -4139,6 +5347,14 @@ "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", "dev": true }, + "json-bigint": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-0.3.0.tgz", + "integrity": "sha1-DM2RLEuCcNBfBW+9E4FLU9OCWx4=", + "requires": { + "bignumber.js": "^7.0.0" + } + }, "json-parse-better-errors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", @@ -4182,34 +5398,449 @@ } } }, - "jsprim": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", - "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "jstransformer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/jstransformer/-/jstransformer-1.0.0.tgz", + "integrity": "sha1-7Yvwkh4vPx7U1cGkT2hwntJHIsM=", + "requires": { + "is-promise": "^2.0.0", + "promise": "^7.0.1" + } + }, + "jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "requires": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "kareem": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.0.tgz", + "integrity": "sha512-6hHxsp9e6zQU8nXsP+02HGWXwTkOEw6IROhF2ZA28cYbUk4eJ6QbtZvdqZOdD9YPKghG3apk5eOCvs+tLl3lRg==" + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + }, + "kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true + }, + "kue": { + "version": "0.11.6", + "resolved": "https://registry.npmjs.org/kue/-/kue-0.11.6.tgz", + "integrity": "sha512-56Jic22qSqdJ3nNpkhVr6RUx/QKalfdBdU0m70hgBKEkhBAgdt6Qr74evec+bM+LGmNbEC6zGGDskX4mcgBYcQ==", + "requires": { + "body-parser": "^1.12.2", + "express": "^4.12.2", + "lodash": "^4.0.0", + "nib": "~1.1.2", + "node-redis-warlock": "~0.2.0", + "pug": "^2.0.0-beta3", + "redis": "~2.6.0-2", + "reds": "^0.2.5", + "stylus": "~0.54.5", + "yargs": "^4.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "camelcase": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", + "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=" + }, + "cliui": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", + "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wrap-ansi": "^2.0.0" + } + }, + "find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "requires": { + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "invert-kv": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", + "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=" + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "lcid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", + "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", + "requires": { + "invert-kv": "^1.0.0" + } + }, + "load-json-file": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "strip-bom": "^2.0.0" + } + }, + "os-locale": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", + "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", + "requires": { + "lcid": "^1.0.0" + } + }, + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "requires": { + "error-ex": "^1.2.0" + } + }, + "path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "requires": { + "pinkie-promise": "^2.0.0" + } + }, + "path-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", + "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", + "requires": { + "graceful-fs": "^4.1.2", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" + }, + "read-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", + "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", + "requires": { + "load-json-file": "^1.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^1.0.0" + } + }, + "read-pkg-up": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", + "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", + "requires": { + "find-up": "^1.0.0", + "read-pkg": "^1.0.0" + } + }, + "require-main-filename": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=" + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "requires": { + "is-utf8": "^0.2.0" + } + }, + "which-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", + "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=" + }, + "window-size": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.2.0.tgz", + "integrity": "sha1-tDFbtCFKPXBY6+7okuE/ok2YsHU=" + }, + "y18n": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", + "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=" + }, + "yargs": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-4.8.1.tgz", + "integrity": "sha1-wMQpJMpKqmsObaFznfshZDn53cA=", + "requires": { + "cliui": "^3.2.0", + "decamelize": "^1.1.1", + "get-caller-file": "^1.0.1", + "lodash.assign": "^4.0.3", + "os-locale": "^1.4.0", + "read-pkg-up": "^1.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^1.0.1", + "which-module": "^1.0.0", + "window-size": "^0.2.0", + "y18n": "^3.2.1", + "yargs-parser": "^2.4.1" + } + }, + "yargs-parser": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-2.4.1.tgz", + "integrity": "sha1-hVaN488VD/SfpRgl8DqMiA3cxcQ=", + "requires": { + "camelcase": "^3.0.0", + "lodash.assign": "^4.0.6" + } + } + } + }, + "kue-ui": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/kue-ui/-/kue-ui-0.1.0.tgz", + "integrity": "sha1-VO9bY6LgZ3dsaA11hIaI1KYED0c=", + "requires": { + "express": "^3.1.1", + "jade": "^1.8.1" + }, + "dependencies": { + "commander": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.6.0.tgz", + "integrity": "sha1-nfflL7Kgyw+4kFjugMMQQiXzfh0=" + }, + "content-disposition": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.0.tgz", + "integrity": "sha1-QoT+auBjCHRjnkToCkGMKTQTXp4=" + }, + "cookie": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.1.3.tgz", + "integrity": "sha1-5zSlwUF/zkctWu+Cw4HKu2TRpDU=" + }, + "debug": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", + "requires": { + "ms": "0.7.1" + } + }, + "depd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.0.1.tgz", + "integrity": "sha1-gK7GTJ1tl+ZcwqnKqTwKpqv3Oqo=" + }, + "destroy": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.3.tgz", + "integrity": "sha1-tDO0ck5x/YVR2YhRdIUcX8N34sk=" + }, + "escape-html": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.2.tgz", + "integrity": "sha1-130y+pjjjC9BroXpJ44ODmuhAiw=" + }, + "etag": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.7.0.tgz", + "integrity": "sha1-A9MLX2fdbmMtKUXTDWZScxo01dg=" + }, + "express": { + "version": "3.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-3.21.2.tgz", + "integrity": "sha1-DCkD7lxU5j1lqWFwdkcDVQZlo94=", + "requires": { + "basic-auth": "~1.0.3", + "commander": "2.6.0", + "connect": "2.30.2", + "content-disposition": "0.5.0", + "content-type": "~1.0.1", + "cookie": "0.1.3", + "cookie-signature": "1.0.6", + "debug": "~2.2.0", + "depd": "~1.0.1", + "escape-html": "1.0.2", + "etag": "~1.7.0", + "fresh": "0.3.0", + "merge-descriptors": "1.0.0", + "methods": "~1.1.1", + "mkdirp": "0.5.1", + "parseurl": "~1.3.0", + "proxy-addr": "~1.0.8", + "range-parser": "~1.0.2", + "send": "0.13.0", + "utils-merge": "1.0.0", + "vary": "~1.0.1" + } + }, + "fresh": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.3.0.tgz", + "integrity": "sha1-ZR+DjiJCTnVm3hYdg1jKoZn4PU8=" + }, + "http-errors": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.3.1.tgz", + "integrity": "sha1-GX4izevUGYWF6GlO9nhhl7ke2UI=", + "requires": { + "inherits": "~2.0.1", + "statuses": "1" + } + }, + "ipaddr.js": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.0.5.tgz", + "integrity": "sha1-X6eM8wG4JceKvDBC2BJyMEnqI8c=" + }, + "merge-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.0.tgz", + "integrity": "sha1-IWnPdTjhsMyH+4jhUC2EdLv3mGQ=" + }, + "mime": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.3.4.tgz", + "integrity": "sha1-EV+eO2s9rylZmDyzjxSaLUDrXVM=" + }, + "ms": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", + "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=" + }, + "proxy-addr": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-1.0.10.tgz", + "integrity": "sha1-DUCoL4Afw1VWfS7LZe/j8HfxIcU=", + "requires": { + "forwarded": "~0.1.0", + "ipaddr.js": "1.0.5" + } + }, + "range-parser": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.0.3.tgz", + "integrity": "sha1-aHKCNTXGkuLCoBA4Jq/YLC4P8XU=" + }, + "send": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.13.0.tgz", + "integrity": "sha1-UY+SGusFYK7H3KspkLFM9vPM5d4=", + "requires": { + "debug": "~2.2.0", + "depd": "~1.0.1", + "destroy": "1.0.3", + "escape-html": "1.0.2", + "etag": "~1.7.0", + "fresh": "0.3.0", + "http-errors": "~1.3.1", + "mime": "1.3.4", + "ms": "0.7.1", + "on-finished": "~2.3.0", + "range-parser": "~1.0.2", + "statuses": "~1.2.1" + } + }, + "statuses": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.2.1.tgz", + "integrity": "sha1-3e1FzBglbVHtQK7BQkidXGECbSg=" + }, + "utils-merge": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.0.tgz", + "integrity": "sha1-ApT7kiu5N1FTVBxPcJYjHyh8ivg=" + }, + "vary": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.0.1.tgz", + "integrity": "sha1-meSYFWaihhGN+yuBc1ffeZM3bRA=" + } + } + }, + "kuler": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-1.0.1.tgz", + "integrity": "sha512-J9nVUucG1p/skKul6DU3PUZrhs0LPulNaeUOox0IyXDi8S4CztTHs1gQphhuZmzXG7VOQSf6NJfKuzteQLv9gQ==", "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.2.3", - "verror": "1.10.0" + "colornames": "^1.1.1" } }, - "kareem": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.0.tgz", - "integrity": "sha512-6hHxsp9e6zQU8nXsP+02HGWXwTkOEw6IROhF2ZA28cYbUk4eJ6QbtZvdqZOdD9YPKghG3apk5eOCvs+tLl3lRg==" - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true - }, - "kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "dev": true - }, "latest-version": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-3.1.0.tgz", @@ -4219,6 +5850,19 @@ "package-json": "^4.0.0" } }, + "lazy-cache": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", + "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=" + }, + "lazystream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.0.tgz", + "integrity": "sha1-9plf4PggOS9hOWvolGJAe7dxaOQ=", + "requires": { + "readable-stream": "^2.0.5" + } + }, "lcid": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", @@ -4288,9 +5932,34 @@ } }, "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" + }, + "lodash.assign": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz", + "integrity": "sha1-DZnzzNem0mHRm9rrkkUAXShYCOc=" + }, + "lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=" + }, + "lodash.difference": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", + "integrity": "sha1-nMtOUF1Ia5FlE0V3KIWi3yf9AXw=" + }, + "lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=" + }, + "lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" }, "lodash.sortby": { "version": "4.7.0", @@ -4298,6 +5967,40 @@ "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", "dev": true }, + "lodash.toarray": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.toarray/-/lodash.toarray-4.4.0.tgz", + "integrity": "sha1-JMS/zWsvuji/0FlNsRedjptlZWE=" + }, + "lodash.union": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", + "integrity": "sha1-SLtQiECfFvGCFmZkHETdGqrjzYg=" + }, + "logform": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.1.2.tgz", + "integrity": "sha512-+lZh4OpERDBLqjiwDLpAWNQu6KMjnlXH2ByZwCuSqVPJletw0kTWJf5CgSNAUKn1KUkv3m2cUz/LK8zyEy7wzQ==", + "requires": { + "colors": "^1.2.1", + "fast-safe-stringify": "^2.0.4", + "fecha": "^2.3.3", + "ms": "^2.1.1", + "triple-beam": "^1.3.0" + }, + "dependencies": { + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, + "longest": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", + "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=" + }, "loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -4444,6 +6147,17 @@ } } }, + "method-override": { + "version": "2.3.10", + "resolved": "https://registry.npmjs.org/method-override/-/method-override-2.3.10.tgz", + "integrity": "sha1-49r41d7hDdLc59SuiNYrvud0drQ=", + "requires": { + "debug": "2.6.9", + "methods": "~1.1.2", + "parseurl": "~1.3.2", + "vary": "~1.1.2" + } + }, "methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", @@ -4498,7 +6212,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, "requires": { "brace-expansion": "^1.1.7" } @@ -4509,9 +6222,9 @@ "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" }, "mixin-deep": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz", - "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", + "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", "dev": true, "requires": { "for-in": "^1.0.2", @@ -4594,6 +6307,28 @@ "resolved": "https://registry.npmjs.org/mongoose-legacy-pluralize/-/mongoose-legacy-pluralize-1.0.2.tgz", "integrity": "sha512-Yo/7qQU4/EyIS8YDFSeenIvXxZN+ld7YdV9LqFVQJzTLye8unujAWPZ4NWKfFA+RNjh+wvTWKY9Z3E5XM6ZZiQ==" }, + "morgan": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.9.1.tgz", + "integrity": "sha512-HQStPIV4y3afTiCYVxirakhlCfGkI161c76kKFca7Fk1JusM//Qeo1ej2XaMniiNeaZklMVrh3vTtIzpzwbpmA==", + "requires": { + "basic-auth": "~2.0.0", + "debug": "2.6.9", + "depd": "~1.1.2", + "on-finished": "~2.3.0", + "on-headers": "~1.0.1" + }, + "dependencies": { + "basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "requires": { + "safe-buffer": "5.1.2" + } + } + } + }, "mpath": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.6.0.tgz", @@ -4641,6 +6376,38 @@ "xtend": "^4.0.0" } }, + "multiparty": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/multiparty/-/multiparty-3.3.2.tgz", + "integrity": "sha1-Nd5oBNwZZD5SSfPT473GyM4wHT8=", + "requires": { + "readable-stream": "~1.1.9", + "stream-counter": "~0.2.0" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + } + } + }, "mute-stream": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", @@ -4673,6 +6440,17 @@ "to-regex": "^3.0.1" } }, + "natural": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/natural/-/natural-0.2.1.tgz", + "integrity": "sha1-HrUVap2QtFkZSeIOlOvHe7Iznto=", + "optional": true, + "requires": { + "apparatus": ">= 0.0.9", + "sylvester": ">= 0.0.12", + "underscore": ">=1.3.1" + } + }, "natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -4690,12 +6468,30 @@ "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==", "dev": true }, + "nib": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/nib/-/nib-1.1.2.tgz", + "integrity": "sha1-amnt5AgblcDe+L4CSkyK4MLLtsc=", + "requires": { + "stylus": "0.54.5" + } + }, "nice-try": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", "dev": true }, + "node-fetch": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", + "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==" + }, + "node-forge": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.8.5.tgz", + "integrity": "sha512-vFMQIWt+J/7FLNyKouZ9TazT74PRV3wgv9UT4cRjC8BffxFbKXkgIWR42URCPSnHm/QDz6BOlb2Q0U4+VQT67Q==" + }, "node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -4721,6 +6517,43 @@ "which": "^1.3.0" } }, + "node-redis-scripty": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/node-redis-scripty/-/node-redis-scripty-0.0.5.tgz", + "integrity": "sha1-S/LTZattqyAswIt6xj+PVarcliU=", + "requires": { + "extend": "^1.2.1", + "lru-cache": "^2.5.0" + }, + "dependencies": { + "extend": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extend/-/extend-1.3.0.tgz", + "integrity": "sha1-0VFvsP9WJNLr+RI+odrFoZlABPg=" + }, + "lru-cache": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", + "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=" + } + } + }, + "node-redis-warlock": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/node-redis-warlock/-/node-redis-warlock-0.2.0.tgz", + "integrity": "sha1-VjlbmUyCjo4y9qrlO5O27fzZeZA=", + "requires": { + "node-redis-scripty": "0.0.5", + "uuid": "^2.0.1" + }, + "dependencies": { + "uuid": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.3.tgz", + "integrity": "sha1-Z+LoY3lyFVMN/zGOW/nc6/1Hsho=" + } + } + }, "nodemon": { "version": "1.19.1", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-1.19.1.tgz", @@ -4769,7 +6602,6 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, "requires": { "hosted-git-info": "^2.1.4", "resolve": "^1.10.0", @@ -4781,7 +6613,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, "requires": { "remove-trailing-separator": "^1.0.1" } @@ -4798,8 +6629,7 @@ "number-is-nan": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "dev": true + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" }, "nwsapi": { "version": "2.1.4", @@ -4851,8 +6681,7 @@ "object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" }, "object-visit": { "version": "1.0.1", @@ -4879,7 +6708,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.0.tgz", "integrity": "sha512-l+H6EQ8qzGRxbkHOd5I/aHRhHDKoQXQ8g0BYt4uSweQU1/J6dZUOyWh9a2Vky35YCKjzmgxOzta2hH6kf9HuXA==", - "dev": true, "requires": { "define-properties": "^1.1.3", "es-abstract": "^1.12.0", @@ -4923,11 +6751,15 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, "requires": { "wrappy": "1" } }, + "one-time": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/one-time/-/one-time-0.0.4.tgz", + "integrity": "sha1-+M33eISCb+Tf+T46nMN7HkSAdC4=" + }, "onetime": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", @@ -5104,8 +6936,7 @@ "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, "path-is-inside": { "version": "1.0.2", @@ -5122,8 +6953,7 @@ "path-parse": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", - "dev": true + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" }, "path-to-regexp": { "version": "0.1.7", @@ -5139,6 +6969,11 @@ "pify": "^3.0.0" } }, + "pause": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/pause/-/pause-0.1.0.tgz", + "integrity": "sha1-68ikqGGf8LioGsFRPDQ0/0af23Q=" + }, "performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", @@ -5149,6 +6984,19 @@ "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=" + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "requires": { + "pinkie": "^2.0.0" + } + }, "pirates": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.1.tgz", @@ -5271,6 +7119,14 @@ "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", "dev": true }, + "promise": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", + "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "requires": { + "asap": "~2.0.3" + } + }, "prompts": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.1.0.tgz", @@ -5286,26 +7142,188 @@ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.5.tgz", "integrity": "sha512-t/7RxHXPH6cJtP0pRG6smSr9QJidhB+3kXu0KgXnbGYMgzEnUxRQ4/LDdfOwZEMyIh3/xHb8PX3t+lfL9z+YVQ==", "requires": { - "forwarded": "~0.1.2", - "ipaddr.js": "1.9.0" + "forwarded": "~0.1.2", + "ipaddr.js": "1.9.0" + } + }, + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", + "dev": true + }, + "psl": { + "version": "1.1.32", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.32.tgz", + "integrity": "sha512-MHACAkHpihU/REGGPLj4sEfc/XKW2bheigvHO1dUqjaKigMp1C8+WLQYRGgeKFMsw5PMfegZcaN8IDXK/cD0+g==" + }, + "pstree.remy": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.7.tgz", + "integrity": "sha512-xsMgrUwRpuGskEzBFkH8NmTimbZ5PcPup0LA8JJkHIm2IMUbQcpo3yeLNWVrufEYjh8YwtSVh0xz6UeWc5Oh5A==", + "dev": true + }, + "pug": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pug/-/pug-2.0.4.tgz", + "integrity": "sha512-XhoaDlvi6NIzL49nu094R2NA6P37ijtgMDuWE+ofekDChvfKnzFal60bhSdiy8y2PBO6fmz3oMEIcfpBVRUdvw==", + "requires": { + "pug-code-gen": "^2.0.2", + "pug-filters": "^3.1.1", + "pug-lexer": "^4.1.0", + "pug-linker": "^3.0.6", + "pug-load": "^2.0.12", + "pug-parser": "^5.0.1", + "pug-runtime": "^2.0.5", + "pug-strip-comments": "^1.0.4" + } + }, + "pug-attrs": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pug-attrs/-/pug-attrs-2.0.4.tgz", + "integrity": "sha512-TaZ4Z2TWUPDJcV3wjU3RtUXMrd3kM4Wzjbe3EWnSsZPsJ3LDI0F3yCnf2/W7PPFF+edUFQ0HgDL1IoxSz5K8EQ==", + "requires": { + "constantinople": "^3.0.1", + "js-stringify": "^1.0.1", + "pug-runtime": "^2.0.5" + } + }, + "pug-code-gen": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/pug-code-gen/-/pug-code-gen-2.0.2.tgz", + "integrity": "sha512-kROFWv/AHx/9CRgoGJeRSm+4mLWchbgpRzTEn8XCiwwOy6Vh0gAClS8Vh5TEJ9DBjaP8wCjS3J6HKsEsYdvaCw==", + "requires": { + "constantinople": "^3.1.2", + "doctypes": "^1.1.0", + "js-stringify": "^1.0.1", + "pug-attrs": "^2.0.4", + "pug-error": "^1.3.3", + "pug-runtime": "^2.0.5", + "void-elements": "^2.0.1", + "with": "^5.0.0" + } + }, + "pug-error": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/pug-error/-/pug-error-1.3.3.tgz", + "integrity": "sha512-qE3YhESP2mRAWMFJgKdtT5D7ckThRScXRwkfo+Erqga7dyJdY3ZquspprMCj/9sJ2ijm5hXFWQE/A3l4poMWiQ==" + }, + "pug-filters": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/pug-filters/-/pug-filters-3.1.1.tgz", + "integrity": "sha512-lFfjNyGEyVWC4BwX0WyvkoWLapI5xHSM3xZJFUhx4JM4XyyRdO8Aucc6pCygnqV2uSgJFaJWW3Ft1wCWSoQkQg==", + "requires": { + "clean-css": "^4.1.11", + "constantinople": "^3.0.1", + "jstransformer": "1.0.0", + "pug-error": "^1.3.3", + "pug-walk": "^1.1.8", + "resolve": "^1.1.6", + "uglify-js": "^2.6.1" + }, + "dependencies": { + "camelcase": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", + "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=" + }, + "cliui": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", + "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", + "requires": { + "center-align": "^0.1.1", + "right-align": "^0.1.1", + "wordwrap": "0.0.2" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + }, + "uglify-js": { + "version": "2.8.29", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", + "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", + "requires": { + "source-map": "~0.5.1", + "uglify-to-browserify": "~1.0.0", + "yargs": "~3.10.0" + } + }, + "wordwrap": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", + "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=" + }, + "yargs": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", + "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", + "requires": { + "camelcase": "^1.0.2", + "cliui": "^2.1.0", + "decamelize": "^1.0.0", + "window-size": "0.1.0" + } + } + } + }, + "pug-lexer": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/pug-lexer/-/pug-lexer-4.1.0.tgz", + "integrity": "sha512-i55yzEBtjm0mlplW4LoANq7k3S8gDdfC6+LThGEvsK4FuobcKfDAwt6V4jKPH9RtiE3a2Akfg5UpafZ1OksaPA==", + "requires": { + "character-parser": "^2.1.1", + "is-expression": "^3.0.0", + "pug-error": "^1.3.3" + } + }, + "pug-linker": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/pug-linker/-/pug-linker-3.0.6.tgz", + "integrity": "sha512-bagfuHttfQOpANGy1Y6NJ+0mNb7dD2MswFG2ZKj22s8g0wVsojpRlqveEQHmgXXcfROB2RT6oqbPYr9EN2ZWzg==", + "requires": { + "pug-error": "^1.3.3", + "pug-walk": "^1.1.8" + } + }, + "pug-load": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/pug-load/-/pug-load-2.0.12.tgz", + "integrity": "sha512-UqpgGpyyXRYgJs/X60sE6SIf8UBsmcHYKNaOccyVLEuT6OPBIMo6xMPhoJnqtB3Q3BbO4Z3Bjz5qDsUWh4rXsg==", + "requires": { + "object-assign": "^4.1.0", + "pug-walk": "^1.1.8" + } + }, + "pug-parser": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pug-parser/-/pug-parser-5.0.1.tgz", + "integrity": "sha512-nGHqK+w07p5/PsPIyzkTQfzlYfuqoiGjaoqHv1LjOv2ZLXmGX1O+4Vcvps+P4LhxZ3drYSljjq4b+Naid126wA==", + "requires": { + "pug-error": "^1.3.3", + "token-stream": "0.0.1" } }, - "pseudomap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", - "dev": true + "pug-runtime": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/pug-runtime/-/pug-runtime-2.0.5.tgz", + "integrity": "sha512-P+rXKn9un4fQY77wtpcuFyvFaBww7/91f3jHa154qU26qFAnOe6SW1CbIDcxiG5lLK9HazYrMCCuDvNgDQNptw==" }, - "psl": { - "version": "1.1.32", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.32.tgz", - "integrity": "sha512-MHACAkHpihU/REGGPLj4sEfc/XKW2bheigvHO1dUqjaKigMp1C8+WLQYRGgeKFMsw5PMfegZcaN8IDXK/cD0+g==" + "pug-strip-comments": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/pug-strip-comments/-/pug-strip-comments-1.0.4.tgz", + "integrity": "sha512-i5j/9CS4yFhSxHp5iKPHwigaig/VV9g+FgReLJWWHEHbvKsbqL0oP/K5ubuLco6Wu3Kan5p7u7qk8A4oLLh6vw==", + "requires": { + "pug-error": "^1.3.3" + } }, - "pstree.remy": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.7.tgz", - "integrity": "sha512-xsMgrUwRpuGskEzBFkH8NmTimbZ5PcPup0LA8JJkHIm2IMUbQcpo3yeLNWVrufEYjh8YwtSVh0xz6UeWc5Oh5A==", - "dev": true + "pug-walk": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pug-walk/-/pug-walk-1.1.8.tgz", + "integrity": "sha512-GMu3M5nUL3fju4/egXwZO0XLi6fW/K3T3VTgFQ14GxNi8btlxgT5qZL//JwZFm/2Fa64J/PNS8AZeys3wiMkVA==" }, "pump": { "version": "3.0.0", @@ -5403,6 +7421,20 @@ "read-pkg": "^3.0.0" } }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, "readdirp": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", @@ -5449,6 +7481,49 @@ "util.promisify": "^1.0.0" } }, + "redis": { + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/redis/-/redis-2.6.5.tgz", + "integrity": "sha1-h8Hv9KSJ+Utwhx89CLaYjyOpVoc=", + "requires": { + "double-ended-queue": "^2.1.0-0", + "redis-commands": "^1.2.0", + "redis-parser": "^2.0.0" + } + }, + "redis-commands": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.5.0.tgz", + "integrity": "sha512-6KxamqpZ468MeQC3bkWmCB1fp56XL64D4Kf0zJSwDZbVLLm7KFkoIcHrgRvQ+sk8dnhySs7+yBg94yIkAK7aJg==" + }, + "redis-parser": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-2.6.0.tgz", + "integrity": "sha1-Uu0J2srBCPGmMcB+m2mUHnoZUEs=" + }, + "reds": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/reds/-/reds-0.2.5.tgz", + "integrity": "sha1-OKdn92Y810kDaEhpfYLHT9KbwB8=", + "optional": true, + "requires": { + "natural": "^0.2.0", + "redis": "^0.12.1" + }, + "dependencies": { + "redis": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/redis/-/redis-0.12.1.tgz", + "integrity": "sha1-ZN92rQ/IrOuuvSoGReikj6xJGF4=", + "optional": true + } + } + }, + "regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" + }, "regex-not": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", @@ -5492,8 +7567,7 @@ "remove-trailing-separator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", - "dev": true + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=" }, "repeat-element": { "version": "1.1.3", @@ -5504,8 +7578,7 @@ "repeat-string": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", - "dev": true + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=" }, "request": { "version": "2.88.0", @@ -5578,8 +7651,7 @@ "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" }, "require-main-filename": { "version": "2.0.0", @@ -5600,7 +7672,6 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.11.1.tgz", "integrity": "sha512-vIpgF6wfuJOZI7KKKSP+HmiKggadPQAdsp5HiC1mvqnfp0gF1vdwgBWZIdrVft9pgqoMFQN+R7BSWZiBxx+BBw==", - "dev": true, "requires": { "path-parse": "^1.0.6" } @@ -5633,6 +7704,15 @@ "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", "dev": true }, + "response-time": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/response-time/-/response-time-2.3.2.tgz", + "integrity": "sha1-/6cbq5UtYvfB1Jt0NDVfvGjf/Fo=", + "requires": { + "depd": "~1.1.0", + "on-headers": "~1.0.1" + } + }, "restore-cursor": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", @@ -5649,6 +7729,14 @@ "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", "dev": true }, + "right-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", + "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", + "requires": { + "align-text": "^0.1.1" + } + }, "rimraf": { "version": "2.6.3", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", @@ -5658,6 +7746,11 @@ "glob": "^7.1.3" } }, + "rndm": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/rndm/-/rndm-1.2.0.tgz", + "integrity": "sha1-8z/pz7Urv9UgqhgyO8ZdsRCht2w=" + }, "rsvp": { "version": "4.8.5", "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz", @@ -5781,6 +7874,86 @@ } } }, + "serve-favicon": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/serve-favicon/-/serve-favicon-2.3.2.tgz", + "integrity": "sha1-3UGeJo3gEqtysxnTN/IQUBP5OB8=", + "requires": { + "etag": "~1.7.0", + "fresh": "0.3.0", + "ms": "0.7.2", + "parseurl": "~1.3.1" + }, + "dependencies": { + "etag": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.7.0.tgz", + "integrity": "sha1-A9MLX2fdbmMtKUXTDWZScxo01dg=" + }, + "fresh": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.3.0.tgz", + "integrity": "sha1-ZR+DjiJCTnVm3hYdg1jKoZn4PU8=" + }, + "ms": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", + "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=" + } + } + }, + "serve-index": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.7.3.tgz", + "integrity": "sha1-egV/xu4o3GP2RWbl+lexEahq7NI=", + "requires": { + "accepts": "~1.2.13", + "batch": "0.5.3", + "debug": "~2.2.0", + "escape-html": "~1.0.3", + "http-errors": "~1.3.1", + "mime-types": "~2.1.9", + "parseurl": "~1.3.1" + }, + "dependencies": { + "accepts": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.2.13.tgz", + "integrity": "sha1-5fHzkoxtlf2WVYw27D2dDeSm7Oo=", + "requires": { + "mime-types": "~2.1.6", + "negotiator": "0.5.3" + } + }, + "debug": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", + "requires": { + "ms": "0.7.1" + } + }, + "http-errors": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.3.1.tgz", + "integrity": "sha1-GX4izevUGYWF6GlO9nhhl7ke2UI=", + "requires": { + "inherits": "~2.0.1", + "statuses": "1" + } + }, + "ms": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", + "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=" + }, + "negotiator": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.5.3.tgz", + "integrity": "sha1-Jp1cR2gQ7JLtvntsLygxY4T5p+g=" + } + } + }, "serve-static": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", @@ -5795,8 +7968,7 @@ "set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", - "dev": true + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" }, "set-value": { "version": "2.0.0", @@ -5868,12 +8040,32 @@ "pkg-conf": "^2.1.0" } }, + "simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=", + "requires": { + "is-arrayish": "^0.3.1" + }, + "dependencies": { + "is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" + } + } + }, "sisteransi": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.0.tgz", "integrity": "sha512-N+z4pHB4AmUv0SjveWRd6q1Nj5w62m5jodv+GD8lvmbY/83T/rpbJGZOnK5T149OldDj4Db07BSv9xY4K6NTPQ==", "dev": true }, + "slang": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/slang/-/slang-0.3.0.tgz", + "integrity": "sha1-E691tPDAGMaoGT1wT2WyO+T7q9w=" + }, "slash": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", @@ -6012,8 +8204,7 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" }, "source-map-resolve": { "version": "0.5.2", @@ -6057,7 +8248,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", - "dev": true, "requires": { "spdx-expression-parse": "^3.0.0", "spdx-license-ids": "^3.0.0" @@ -6066,14 +8256,12 @@ "spdx-exceptions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", - "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==", - "dev": true + "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==" }, "spdx-expression-parse": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", - "dev": true, "requires": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" @@ -6082,8 +8270,7 @@ "spdx-license-ids": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.4.tgz", - "integrity": "sha512-7j8LYJLeY/Yb6ACbQ7F76qy5jHkp0U6jgBfJsk97bwWlVUnUWsAgpyaCvo17h0/RQGnQ036tVDomiwoI4pDkQA==", - "dev": true + "integrity": "sha512-7j8LYJLeY/Yb6ACbQ7F76qy5jHkp0U6jgBfJsk97bwWlVUnUWsAgpyaCvo17h0/RQGnQ036tVDomiwoI4pDkQA==" }, "split-string": { "version": "3.1.0", @@ -6116,6 +8303,11 @@ "tweetnacl": "~0.14.0" } }, + "stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=" + }, "stack-utils": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.2.tgz", @@ -6154,6 +8346,37 @@ "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=", "dev": true }, + "stream-counter": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/stream-counter/-/stream-counter-0.2.0.tgz", + "integrity": "sha1-3tJmVWMZyLDiIoErnPOyb6fZR94=", + "requires": { + "readable-stream": "~1.1.8" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + } + } + }, "streamsearch": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", @@ -6179,6 +8402,14 @@ "strip-ansi": "^4.0.0" } }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + }, "strip-ansi": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", @@ -6205,6 +8436,47 @@ "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", "dev": true }, + "stylus": { + "version": "0.54.5", + "resolved": "https://registry.npmjs.org/stylus/-/stylus-0.54.5.tgz", + "integrity": "sha1-QrlWCTHKcJDOhRWnmLqeaqPW3Hk=", + "requires": { + "css-parse": "1.7.x", + "debug": "*", + "glob": "7.0.x", + "mkdirp": "0.5.x", + "sax": "0.5.x", + "source-map": "0.1.x" + }, + "dependencies": { + "glob": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.0.6.tgz", + "integrity": "sha1-IRuvr0nlJbjNkyYNFKsTYVKz9Xo=", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.2", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "sax": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/sax/-/sax-0.5.8.tgz", + "integrity": "sha1-1HLbIo6zMcJQaw6MFVJK25OdEsE=" + }, + "source-map": { + "version": "0.1.43", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz", + "integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=", + "requires": { + "amdefine": ">=0.0.4" + } + } + } + }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -6213,6 +8485,12 @@ "has-flag": "^3.0.0" } }, + "sylvester": { + "version": "0.0.21", + "resolved": "https://registry.npmjs.org/sylvester/-/sylvester-0.0.21.tgz", + "integrity": "sha1-KYexzivS84sNzio0OIiEv6RADqc=", + "optional": true + }, "symbol-tree": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.2.tgz", @@ -6259,6 +8537,20 @@ } } }, + "tar-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz", + "integrity": "sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==", + "requires": { + "bl": "^1.0.0", + "buffer-alloc": "^1.2.0", + "end-of-stream": "^1.0.0", + "fs-constants": "^1.0.0", + "readable-stream": "^2.3.0", + "to-buffer": "^1.1.1", + "xtend": "^4.0.0" + } + }, "term-size": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/term-size/-/term-size-1.2.0.tgz", @@ -6314,6 +8606,11 @@ "require-main-filename": "^2.0.0" } }, + "text-hex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", + "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==" + }, "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -6353,6 +8650,11 @@ "integrity": "sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE=", "dev": true }, + "to-buffer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz", + "integrity": "sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg==" + }, "to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", @@ -6406,6 +8708,11 @@ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" }, + "token-stream": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/token-stream/-/token-stream-0.0.1.tgz", + "integrity": "sha1-zu78cXp2xDFvEm0LnbqlXX598Bo=" + }, "touch": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", @@ -6434,18 +8741,83 @@ "punycode": "^2.1.0" } }, + "transformers": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/transformers/-/transformers-2.1.0.tgz", + "integrity": "sha1-XSPLNVYd2F3Gf7hIIwm0fVPM6ac=", + "requires": { + "css": "~1.0.8", + "promise": "~2.0", + "uglify-js": "~2.2.5" + }, + "dependencies": { + "is-promise": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-1.0.1.tgz", + "integrity": "sha1-MVc3YcBX4zwukaq56W2gjO++duU=" + }, + "optimist": { + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.3.7.tgz", + "integrity": "sha1-yQlBrVnkJzMokjB00s8ufLxuwNk=", + "requires": { + "wordwrap": "~0.0.2" + } + }, + "promise": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/promise/-/promise-2.0.0.tgz", + "integrity": "sha1-RmSKqdYFr10ucMMCS/WUNtoCuA4=", + "requires": { + "is-promise": "~1" + } + }, + "source-map": { + "version": "0.1.43", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz", + "integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=", + "requires": { + "amdefine": ">=0.0.4" + } + }, + "uglify-js": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.2.5.tgz", + "integrity": "sha1-puAqcNg5eSuXgEiLe4sYTAlcmcc=", + "requires": { + "optimist": "~0.3.5", + "source-map": "~0.1.7" + } + }, + "wordwrap": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=" + } + } + }, "trim-right": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", "dev": true }, + "triple-beam": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.3.0.tgz", + "integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==" + }, "tslib": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==", "dev": true }, + "tsscmp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.5.tgz", + "integrity": "sha1-fcSjOvcVgatDN9qR2FylQn69mpc=" + }, "tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -6493,6 +8865,12 @@ "source-map": "~0.6.1" } }, + "uglify-to-browserify": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", + "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", + "optional": true + }, "uid-safe": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", @@ -6510,6 +8888,12 @@ "debug": "^2.2.0" } }, + "underscore": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.1.tgz", + "integrity": "sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg==", + "optional": true + }, "union-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz", @@ -6669,12 +9053,29 @@ "prepend-http": "^1.0.1" } }, + "url-template": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz", + "integrity": "sha1-/FZaPMy/93MMd19WQflVV5FDnyE=" + }, "use": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", "dev": true }, + "util": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.0.tgz", + "integrity": "sha512-pPSOFl7VLhZ7LO/SFABPraZEEurkJUWSMn3MuA/r3WQZc+Z1fqou2JqLSOZbCLl73EUIxuUVX8X4jkX2vfJeAA==", + "requires": { + "inherits": "2.0.3", + "is-arguments": "^1.0.4", + "is-generator-function": "^1.0.7", + "object.entries": "^1.1.0", + "safe-buffer": "^5.1.2" + } + }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -6704,7 +9105,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, "requires": { "spdx-correct": "^3.0.0", "spdx-expression-parse": "^3.0.0" @@ -6730,6 +9130,16 @@ "extsprintf": "^1.2.0" } }, + "vhost": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/vhost/-/vhost-3.0.2.tgz", + "integrity": "sha1-L7HezUxGaqiLD5NBrzPcGv8keNU=" + }, + "void-elements": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", + "integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=" + }, "w3c-hr-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.1.tgz", @@ -6804,6 +9214,88 @@ "string-width": "^2.1.1" } }, + "window-size": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", + "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=" + }, + "winston": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.2.1.tgz", + "integrity": "sha512-zU6vgnS9dAWCEKg/QYigd6cgMVVNwyTzKs81XZtTFuRwJOcDdBg7AU0mXVyNbs7O5RH2zdv+BdNZUlx7mXPuOw==", + "requires": { + "async": "^2.6.1", + "diagnostics": "^1.1.1", + "is-stream": "^1.1.0", + "logform": "^2.1.1", + "one-time": "0.0.4", + "readable-stream": "^3.1.1", + "stack-trace": "0.0.x", + "triple-beam": "^1.3.0", + "winston-transport": "^4.3.0" + }, + "dependencies": { + "readable-stream": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz", + "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, + "winston-transport": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.3.0.tgz", + "integrity": "sha512-B2wPuwUi3vhzn/51Uukcao4dIduEiPOcOt9HJ3QeaXgkJ5Z7UwpBzxS4ZGNHtrxrUvTwemsQiSys0ihOf8Mp1A==", + "requires": { + "readable-stream": "^2.3.6", + "triple-beam": "^1.2.0" + } + }, + "with": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/with/-/with-5.1.1.tgz", + "integrity": "sha1-+k2qktrzLE6pTtRTyB8EaGtXXf4=", + "requires": { + "acorn": "^3.1.0", + "acorn-globals": "^3.0.0" + }, + "dependencies": { + "acorn": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", + "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=" + }, + "acorn-globals": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-3.1.0.tgz", + "integrity": "sha1-/YJw9x+7SZawBPqIDuXUZXOnMb8=", + "requires": { + "acorn": "^4.0.4" + }, + "dependencies": { + "acorn": { + "version": "4.0.13", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz", + "integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c=" + } + } + } + } + }, + "wkhtmltopdf": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/wkhtmltopdf/-/wkhtmltopdf-0.3.4.tgz", + "integrity": "sha1-4ttUKn3CJIAyvxNnVLXN188oFfA=", + "requires": { + "is-stream": "^1.0.1", + "slang": ">=0.2" + } + }, "wordwrap": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", @@ -6814,7 +9306,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", - "dev": true, "requires": { "string-width": "^1.0.1", "strip-ansi": "^3.0.1" @@ -6823,14 +9314,12 @@ "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" }, "is-fullwidth-code-point": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, "requires": { "number-is-nan": "^1.0.0" } @@ -6839,7 +9328,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -6850,7 +9338,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, "requires": { "ansi-regex": "^2.0.0" } @@ -6860,8 +9347,7 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "write": { "version": "1.0.3", @@ -6972,6 +9458,16 @@ "camelcase": "^5.0.0", "decamelize": "^1.2.0" } + }, + "zip-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-2.0.1.tgz", + "integrity": "sha512-c+eUhhkDpaK87G/py74wvWLtz2kzMPNCCkUApkun50ssE0oQliIQzWpTnwjB+MTKVIf2tGzIgHyqW/Y+W77ecQ==", + "requires": { + "archiver-utils": "^2.0.0", + "compress-commons": "^1.2.0", + "readable-stream": "^2.0.0" + } } } } diff --git a/package.json b/package.json index c8d93ee..09ca533 100644 --- a/package.json +++ b/package.json @@ -26,22 +26,36 @@ }, "dependencies": { "@sendgrid/mail": "^6.4.0", + "archiver": "^3.0.0", "bcryptjs": "^2.4.3", "body-parser": "^1.19.0", + "connect-mongo": "^3.0.0", "cookie-parser": "^1.4.4", "cors": "^2.8.5", + "delay": "^4.3.0", "dotenv": "^8.0.0", + "easy-pdf-merge": "^0.2.1", + "ejs": "^2.6.2", "express": "^4.17.1", "express-session": "^1.16.1", "express-validator": "^5.3.1", + "googleapis": "^39.2.0", "http-errors": "^1.7.2", + "http-status-codes": "^1.3.2", + "kue": "^0.11.6", + "kue-ui": "^0.1.0", "libphonenumber-js": "^1.7.19", + "mkdirp": "^0.5.1", "mongo-sanitize": "^1.0.1", "mongoose": "^5.5.14", + "morgan": "^1.9.1", "multer": "^1.4.1", "randomstring": "^1.1.5", "signale": "^1.4.0", - "validator": "^11.0.0" + "util": "^0.12.0", + "validator": "^11.0.0", + "winston": "^3.2.1", + "wkhtmltopdf": "^0.3.4" }, "devDependencies": { "babel-eslint": "^10.0.1", diff --git a/public/fonts/Montserrat/Montserrat-Black.ttf b/public/fonts/Montserrat/Montserrat-Black.ttf new file mode 100644 index 0000000..8747a33 Binary files /dev/null and b/public/fonts/Montserrat/Montserrat-Black.ttf differ diff --git a/public/fonts/Montserrat/Montserrat-Bold.ttf b/public/fonts/Montserrat/Montserrat-Bold.ttf new file mode 100644 index 0000000..1a1edbf Binary files /dev/null and b/public/fonts/Montserrat/Montserrat-Bold.ttf differ diff --git a/public/fonts/Montserrat/Montserrat-ExtraBold.ttf b/public/fonts/Montserrat/Montserrat-ExtraBold.ttf new file mode 100644 index 0000000..236f910 Binary files /dev/null and b/public/fonts/Montserrat/Montserrat-ExtraBold.ttf differ diff --git a/public/fonts/Montserrat/Montserrat-ExtraLight.ttf b/public/fonts/Montserrat/Montserrat-ExtraLight.ttf new file mode 100644 index 0000000..d334011 Binary files /dev/null and b/public/fonts/Montserrat/Montserrat-ExtraLight.ttf differ diff --git a/public/fonts/Montserrat/Montserrat-Light.ttf b/public/fonts/Montserrat/Montserrat-Light.ttf new file mode 100644 index 0000000..b27ed85 Binary files /dev/null and b/public/fonts/Montserrat/Montserrat-Light.ttf differ diff --git a/public/fonts/Montserrat/Montserrat-Medium.ttf b/public/fonts/Montserrat/Montserrat-Medium.ttf new file mode 100644 index 0000000..51a8d65 Binary files /dev/null and b/public/fonts/Montserrat/Montserrat-Medium.ttf differ diff --git a/public/fonts/Montserrat/Montserrat-Regular.ttf b/public/fonts/Montserrat/Montserrat-Regular.ttf new file mode 100644 index 0000000..f7d9761 Binary files /dev/null and b/public/fonts/Montserrat/Montserrat-Regular.ttf differ diff --git a/public/fonts/Montserrat/Montserrat-SemiBold.ttf b/public/fonts/Montserrat/Montserrat-SemiBold.ttf new file mode 100644 index 0000000..b4a169c Binary files /dev/null and b/public/fonts/Montserrat/Montserrat-SemiBold.ttf differ diff --git a/public/fonts/Montserrat/Montserrat-Thin.ttf b/public/fonts/Montserrat/Montserrat-Thin.ttf new file mode 100644 index 0000000..ec41b06 Binary files /dev/null and b/public/fonts/Montserrat/Montserrat-Thin.ttf differ diff --git a/public/fonts/Montserrat/OFL.txt b/public/fonts/Montserrat/OFL.txt new file mode 100644 index 0000000..7881887 --- /dev/null +++ b/public/fonts/Montserrat/OFL.txt @@ -0,0 +1,93 @@ +Copyright 2011 The Montserrat Project Authors (https://github.com/JulietaUla/Montserrat) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/public/uploads/.gitignore b/public/uploads/.gitignore new file mode 100644 index 0000000..c96a04f --- /dev/null +++ b/public/uploads/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/scripts/clean_project.sh b/scripts/clean_project.sh new file mode 100755 index 0000000..925096d --- /dev/null +++ b/scripts/clean_project.sh @@ -0,0 +1,23 @@ +#!/bin/sh + +echo "**** This script needs sudo access, run with sudo." +echo "**** This script deletes all of your docker mounted database data and log files, so think before running this script. +" + +# Prompt confirmation +echo -n "Are you sure you want to clear all data(mongodb,redis) and logs (Y/n)? " +read answer +if [ "$answer" != "${answer#[Yy]}" ] ; +then + + echo "Removing mongodb data and redis data..." + sleep 3s + rm -rf ../docker/data + + echo "Removing all logs..." + sleep 3s + rm -rf ../storage/logs/*.log + + echo "All data and logs are removed." + +fi \ No newline at end of file diff --git a/scripts/install_packages.sh b/scripts/install_packages.sh new file mode 100755 index 0000000..6715550 --- /dev/null +++ b/scripts/install_packages.sh @@ -0,0 +1,7 @@ +apk update && \ +apk add bash && \ +apk add openjdk8-jre && \ +apk add xvfb && \ +apk add vim && \ +apk add nano && \ +apk add wkhtmltopdf diff --git a/scripts/node_app_entry.sh b/scripts/node_app_entry.sh new file mode 100755 index 0000000..bfab353 --- /dev/null +++ b/scripts/node_app_entry.sh @@ -0,0 +1,24 @@ +#!/bin/sh + +sleep 20 + +echo "" +echo "" + +echo " ###############################################" +echo "" +echo " ***** Entering node_app_entry ***** " +echo " ***** Starting kue workers ***** " + +nohup node ./src/workers/index.js & + +echo " ***** Starting the Node application *****" +node ./index.js -b 0.0.0.0:8000 +echo "" +echo " ###############################################" +echo "" +echo "" + +echo " ***** Exiting app ***** " +echo " ###############################################" +echo "" diff --git a/src/config/config.js b/src/config/config.js index c4ddfdc..b2b76e4 100644 --- a/src/config/config.js +++ b/src/config/config.js @@ -1,14 +1,17 @@ -require('dotenv').config( - { - path: __dirname+'/./../../.env' - }, - { - debug: process.env.DEBUG - } -) +const dotenv = require('dotenv'); +const path = require('path'); + + +// Declaring globals +const BASE_DIR = __dirname + '/./../../'; + +dotenv.config( + { + path: __dirname + '/./../../.env' + } +); module.exports = { - CORS_ORIGIN: process.env.CORS_ORIGIN, email: { SITE_NOREPLY_EMAIL: process.env.SITE_NOREPLY_EMAIL }, @@ -20,20 +23,56 @@ module.exports = { START_YEAR: process.env.START_YEAR, END_YEAR: process.env.END_YEAR }, + debug: process.env.DEBUG.toLowerCase()=='true' ? true : false, + maintenance: process.env.MAINTENANCE.toLowerCase()=='true' ? true : false, + directory: { + BASE_DIR: BASE_DIR, + CLIENT_BASE_DIR: process.env.CLIENT_BASE_DIR, + CONFIG_DIR: path.join(BASE_DIR, 'src', 'config'), + LOGS_DIR: path.join(BASE_DIR, 'storage', 'logs'), + PUBLIC_DIR: path.join(process.env.CLIENT_BASE_DIR, 'public'), + SRC_DIR: path.join(BASE_DIR, 'src'), + APP_STATIC_DIR: path.join(BASE_DIR, 'public'), + UPLOADS_DIR: path.join(process.env.CLIENT_BASE_DIR, 'public', 'uploads') + }, + environment: process.env.environment, + files: { + archive: { + format: 'zip', + mimeType: 'application/zip' + }, + mentorsList : path.join(__dirname, 'mentorsList.json') + }, key: { - SENDGRID_API_KEY: process.env.SENDGRID_API_KEY + SENDGRID_API_KEY: process.env.SENDGRID_API_KEY, + google_auth: { + credentials: path.join(__dirname, 'google_auth_credentials.json'), + service_jwt: path.join(__dirname, 'google_auth_service_jwt.json'), + TOKEN_PATH: path.join(__dirname, 'google_auth_token.json') + }, }, - mongodb: { + mongodb: { + DB_HOST: process.env.DB_HOST, + DB_PORT: process.env.DB_PORT, + DB_AUTH_ENABLED: process.env.DB_AUTH_ENABLED.toLowerCase()=='true' ? true : false, + DB_AUTH_SOURCE: process.env.DB_AUTH_SOURCE, + DB_NAME: process.env.DB_NAME, DB_USERNAME: process.env.DB_USERNAME, - DB_PASSWORD: process.env.DB_PASSWORD, - DB_URI: process.env.DB_URI - }, - PORT: process.env.PORT, - session: { - secretString: process.env.SESSION_SECRET + DB_PASSWORD: process.env.DB_PASSWORD + }, + ports: { + APP_PORT: process.env.APP_PORT, + KUE_PORT: process.env.KUE_PORT + }, + redis: { + HOST: process.env.REDIS_HOST, + PORT: process.env.REDIS_PORT + }, + session: { + secretString: process.env.SESSION_SECRET }, url: { - API_URL: process.env.API_URL, - CLIENT_URL: process.env.CLIENT_URL + API_BASE_URL: process.env.API_BASE_URL, + CLIENT_BASE_URL: process.env.CLIENT_BASE_URL } -}; \ No newline at end of file +}; diff --git a/src/config/google_auth_credentials.example.json b/src/config/google_auth_credentials.example.json new file mode 100644 index 0000000..d05c2fe --- /dev/null +++ b/src/config/google_auth_credentials.example.json @@ -0,0 +1,14 @@ +{ + "installed": { + "client_id": "", + "project_id": "", + "auth_uri": "https://accounts.google.com/o/oauth2/auth", + "token_uri": "https://oauth2.googleapis.com/token", + "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", + "client_secret": "", + "redirect_uris": [ + "", + "http://localhost" + ] + } +} \ No newline at end of file diff --git a/src/config/google_auth_service_jwt.example.json b/src/config/google_auth_service_jwt.example.json new file mode 100644 index 0000000..cb97af8 --- /dev/null +++ b/src/config/google_auth_service_jwt.example.json @@ -0,0 +1,13 @@ +{ + "type": "service_account", + "project_id": "", + "private_key_id": "", + "private_key": "", + "client_email": "*.iam.gserviceaccount.com", + "client_id": "", + "auth_uri": "https://accounts.google.com/o/oauth2/auth", + "token_uri": "https://oauth2.googleapis.com/token", + "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", + "client_x509_cert_url": "" + } + \ No newline at end of file diff --git a/src/config/mentorsList.example.json b/src/config/mentorsList.example.json new file mode 100644 index 0000000..5771f66 --- /dev/null +++ b/src/config/mentorsList.example.json @@ -0,0 +1,8 @@ +[ + { + "email": "johndoe@example.com" + }, + { + "email": "abc@example.com" + } +] \ No newline at end of file diff --git a/src/config/winston.js b/src/config/winston.js new file mode 100644 index 0000000..e543e86 --- /dev/null +++ b/src/config/winston.js @@ -0,0 +1,81 @@ +const path = require('path'); +const winston = require('winston'); + +// Importing configuration file +const config = require('../config/config'); + + +// Define the custom settings for each transport (file, console) +var transportOptions = { + infoFile: { + level: 'info', + filename: path.join(config.directory.LOGS_DIR, 'info_logs.log'), + handleExceptions: true, + json: true, + maxsize: 5242880, // 5MB + maxFiles: 10, + colorize: true, + }, + warnFile: { + level: 'warn', + filename: path.join(config.directory.LOGS_DIR, 'warn_logs.log'), + handleExceptions: true, + json: true, + maxsize: 5242880, // 5MB + maxFiles: 10, + colorize: true, + }, + errorFile: { + level: 'error', + filename: path.join(config.directory.LOGS_DIR, 'error_logs.log'), + handleExceptions: true, + json: true, + maxsize: 5242880, // 5MB + maxFiles: 10, + colorize: true, + }, + console: { + level: 'info', + handleExceptions: true, + json: false, + colorize: true, + }, +}; + +// Configure different transports based on environment +let transports = new Array(); +// Push production/error level transports +transports.push(new winston.transports.File(transportOptions.errorFile)); + +// Push development level transports +if(config.environment=='development'){ + transports.push(new winston.transports.Console(transportOptions.console)); + transports.push(new winston.transports.File(transportOptions.infoFile)); + transports.push(new winston.transports.File(transportOptions.warnFile)); +} + + +// Setting format of logs +let formats = new Array(); +formats.push(winston.format.splat()); +formats.push(winston.format.timestamp({ + format: 'YYYY-MM-DD HH:mm:ss' +})); +formats.push(winston.format.json()); + +// Instantiate a new Winston Logger with the settings defined above +var logger = winston.createLogger({ + format: winston.format.combine.apply(null, formats), + transports: transports, + exitOnError: false, // do not exit on handled exceptions +}); + +// Create a stream object with a 'write' function that will be used by `morgan` +logger.stream = { + write: function (message, encoding) { + // Use the 'info' log level so the output will be picked up by both transports (file and console) + logger.info(message); + }, +}; + +module.exports = logger; \ No newline at end of file diff --git a/src/controllers/abstract.controller.js b/src/controllers/abstract.controller.js index c611aa5..d67e385 100644 --- a/src/controllers/abstract.controller.js +++ b/src/controllers/abstract.controller.js @@ -1,14 +1,187 @@ +const HttpStatus = require('http-status-codes'); +const path = require('path'); +const randomstring = require('randomstring'); +const sanitize = require('mongo-sanitize'); + +// Importing config/env variables +const logger = require('../config/winston'); + // Importing models const Student = require('../models/student.model'); -exports.uploadDocLink = async (req, res, next) => { +//Importing utils +const mentorsUtil = require('../utils/mentors.util'); +const Uploader = require('../utils/upload.util'); + +exports.sendDocLink = async (req, res) => { + try{ + if(!req.body.docLink){ + logger.warn('Invalid parameters'); + let status_code = 400; + return res.status(status_code).json({ + status_code: status_code, + message: HttpStatus.getStatusText(status_code), + data: {} + }); + } + + const email = req.session.student.email; + let docLink = sanitize(req.body.docLink); + const student = await Student.findOne({email: email}).exec(); + student.abstract.docLink = docLink; + + let submissionType = 'Project Abstract'; + let mailJobs = await mentorsUtil.createMailJobs(student.personalInfo.name, docLink, submissionType); + if(mailJobs.status_code!=200){ + throw(mailJobs.message); + } + + await student.save(); + + logger.info(`Successfully created mailing jobs and saved abstract's doc link for email: ${student.email}.`); + return res.status(200).json({ + status_code: 200, + message: `Successfully saved abstract's doc link`, + data: {} + }); + } catch(error){ + logger.error(error.toString()); + let status_code = 500; + return res.status(status_code).json({ + status_code: status_code, + message: HttpStatus.getStatusText(status_code), + data: {} + }); + } } -exports.uploadFinalAbstract = async (req, res, next) => { - +exports.uploadFinalAbstract = async (req, res) => { + try{ + const email = req.session.student.email; + let student = await Student.findOne({email: email}).exec(); + let fieldName = 'projectAbstract'; + let fileName = student.applicationNumber + '_' + fieldName + '_' + randomstring.generate(4); + const fileOptions = { + fileName: fileName, + fieldName: fieldName, + allowedFileSize : 3000000, // 3 MB + allowedFileTypesRE: /pdf/, + } + let fileUploader = new Uploader(req,student.applicationNumber,fileOptions); + let uploadResponse = await fileUploader.uploadSingle(req,res,fieldName); + + if(uploadResponse.status_code!=200){ + logger.warn(uploadResponse.message); + return res.status(uploadResponse.status_code).json({ + status_code: uploadResponse.status_code, + message: uploadResponse.message, + data: {} + }); + } + + let location = path.join('uploads',student.applicationNumber,uploadResponse.data.file.filename); + student.abstract.projectAbstract = location; + await student.save(); + logger.info(`Successfully uploaded project abstract for email: ${student.email}`); + + return res.status(uploadResponse.status_code).json({ + status_code: uploadResponse.status_code, + message: uploadResponse.message, + data: { + filePath: student.abstract.projectAbstract + } + }); + + } catch(error){ + logger.error(error.toString()); + let status_code = 500; + return res.status(status_code).json({ + status_code: status_code, + message: HttpStatus.getStatusText(status_code), + data: {} + }); + } } -exports.viewAbstract = async (req, res, next) => { +exports.uploadSupportingFiles = async (req, res) => { + try{ + const email = req.session.student.email; + let student = await Student.findOne({email: email}).exec(); + let fieldName = 'supportingFiles'; + let fileName = student.applicationNumber + '_' + fieldName + '_' + randomstring.generate(4); + const fileOptions = { + fileName: fileName, + fieldName: fieldName, + allowedFileSize : 3000000, // 3 MB + allowedFileTypesRE: /jpeg|jpg|png|avi|flv|mov|mp4|mkv|wmv/, + } + let fieldDetails = { + fieldName: fieldName, + maxCount: 2 + } + let fileUploader = new Uploader(req,student.applicationNumber,fileOptions); + let uploadResponse = await fileUploader.uploadMulti(req,res,fieldDetails); + + if(uploadResponse.status_code!=200){ + logger.warn(uploadResponse.message); + return res.status(uploadResponse.status_code).json({ + status_code: uploadResponse.status_code, + message: uploadResponse.message, + data: {} + }); + } + + var locationArray = new Array(); + for(let i=0;i { + try{ + const email = req.session.student.email; + const student = await Student.findOne({email: email}).select('abstract applicationNumber -_id').exec(); + + logger.info(`Successfully retrieved abstract details for email: ${student.email}`); + let status_code = 200; + return res.status(status_code).json({ + status_code: status_code, + message: HttpStatus.getStatusText(status_code), + data: { + applicationNumber: student.applicationNumber, + abstract: student.abstract + } + }); + } catch(error){ + logger.error(error.toString()); + let status_code = 500; + return res.status(status_code).json({ + status_code: status_code, + message: HttpStatus.getStatusText(status_code), + data: {} + }); + } } \ No newline at end of file diff --git a/src/controllers/auth/student.controller.js b/src/controllers/auth/student.controller.js index a0ddce8..3ad5ebb 100644 --- a/src/controllers/auth/student.controller.js +++ b/src/controllers/auth/student.controller.js @@ -1,10 +1,13 @@ +const HttpStatus = require('http-status-codes'); +const mkdirp = require('mkdirp'); +const path = require('path'); const randomstring = require('randomstring'); const sanitize = require('mongo-sanitize'); -const signale = require('signale'); const validator = require('validator'); // Importing config/env variables const config = require('../../config/config'); +const logger = require('../../config/winston'); // Importing models const Student = require('../../models/student.model'); @@ -17,47 +20,23 @@ const sendgridMailUtil = require('../../utils/sendgridMail.util'); exports.sendVerificationCode = async (req, res) => { try{ if(!req.body.email || !validator.isEmail(req.body.email)){ - throw Error('Invalid parameters'); + logger.warn('Invalid parameters'); + let status_code = 400; + return res.status(status_code).json({ + status_code: status_code, + message: HttpStatus.getStatusText(status_code), + data: {} + }); } const email = sanitize(req.body.email); - const studentExists = await Student.findOne({email: email}).exec(); - if(studentExists){ - throw Error('Email already exists!'); - } - let student = new Student(); - student.email = email; - student.verificationCode = await randomstring.generate(6); - student.isVerified1 = false; - - let mailResponse = await sendgridMailUtil.sendVerificationCode(student.email,student.verificationCode); - if(mailResponse.status_code!=200){ - throw Error(mailResponse.message); - } - - let newStudent = await student.save(); + let student = await Student.findOne({email: email}).exec(); - return res.json({ - status_code: 200, - message: `An email with verification code has been sent to your email: ${newStudent.email}` - }); - } catch(error){ - return res.status(400).json({ - status_code: 400, - message: error.toString() - }); - } -} - -exports.reSendVerificationCode = async (req, res) => { - try{ - if(!req.body.email || !validator.isEmail(req.body.email)){ - throw Error('Invalid parameters'); - } - const email = req.body.email; - const student = await Student.findOne({email: email}).exec(); + // If student with email does not exist if(!student){ - throw Error('Sorry, this email is not registered. Please register with your email first.'); + student = new Student(); + student.email = email; } + student.verificationCode = randomstring.generate(6); student.isVerified1 = false; @@ -68,42 +47,65 @@ exports.reSendVerificationCode = async (req, res) => { let newStudent = await student.save(); - return res.json({ - status_code: 200, - message: `An email with new verification code has been sent to your email: ${newStudent.email}` + logger.info(`An email with verification code has been sent to your email: ${newStudent.email}`); + let status_code = 200; + return res.status(status_code).json({ + status_code: status_code, + message: `An email with verification code has been sent to your email: ${newStudent.email}`, + data: {} }); } catch(error){ - return res.status(400).json({ - status_code: 400, - message: error.toString() + logger.error(error.toString()); + let status_code = 500; + return res.status(status_code).json({ + status_code: status_code, + message: HttpStatus.getStatusText(status_code), + data: {} }); } } -exports.verifyEmail = async (req, res) => { +exports.checkVerificationCode = async (req, res) => { try{ - if(!req.body.email || !validator.isEmail(req.body.email) || !req.body.verificationCode){ - throw Error('Invalid parameters'); + if(!req.query.email || !validator.isEmail(req.query.email) || !req.query.verificationCode){ + logger.warn('Invalid parameters'); + let status_code = 400; + return res.status(status_code).json({ + status_code: status_code, + message: HttpStatus.getStatusText(status_code), + data: {} + }); } - const email = req.body.email; - const verificationCode = req.body.verificationCode; + const email = req.query.email; + const verificationCode = req.query.verificationCode; const student = await Student.findOne({ email: email, verificationCode: verificationCode }).exec(); if(!student){ - throw Error('Incorrect verification code.'); + logger.warn(`Please check your email: ${email} and verification code.`); + return res.status(400).json({ + status_code: 400, + message: 'Please check your email and verification code.', + data: {} + }); } - res.status(200).json({ - status_code: 200, - message: 'Success' + logger.info(`Email verification code success for email: ${student.email}`); + let status_code = 200; + return res.status(status_code).json({ + status_code: status_code, + message: HttpStatus.getStatusText(status_code), + data: {} }); } catch(error){ - res.status(400).json({ - status_code: 400, - message: error.toString() + logger.error(error.toString()); + let status_code = 500; + return res.status(status_code).json({ + status_code: status_code, + message: HttpStatus.getStatusText(status_code), + data: {} }); } } @@ -111,144 +113,298 @@ exports.verifyEmail = async (req, res) => { exports.sendInstiVerificationCode = async (req, res) => { try{ if(!req.body.instiEmail || !validator.isEmail(req.body.instiEmail)){ - throw Error('Invalid parameters'); + logger.warn('Invalid parameters'); + let status_code = 400; + return res.status(status_code).json({ + status_code: status_code, + message: HttpStatus.getStatusText(status_code), + data: {} + }); + } + if(!registerUtil.checkEduEmail(req.body.instiEmail)){ + logger.warn(`Please enter a valid institute(.edu) email address : ${req.body.instiEmail}`); + let status_code = 400; + return res.status(status_code).json({ + status_code: status_code, + message: 'Please enter a valid institute(.edu) email address', + data: {} + }); } const email = req.session.student.email; const instiEmail = sanitize(req.body.instiEmail); - const instiEmailExists = await Student.findOne({instiEmail: instiEmail}).exec(); - if(instiEmailExists){ - throw Error('Institute email already exists!'); - } - let student = await Student.findOne({email: email}).exec(); + student.instiEmail = instiEmail; - student.instiVerificationCode = randomstring.generate(6); + student.verificationCode = randomstring.generate(6); student.isVerified2 = false; - let mailResponse = await sendgridMailUtil.sendVerificationCode(student.instiEmail,student.instiVerificationCode); + let mailResponse = await sendgridMailUtil.sendVerificationCode(student.instiEmail,student.verificationCode); if(mailResponse.status_code!=200){ throw Error(mailResponse.message); } let newStudent = await student.save(); - return res.json({ - status_code: 200, - message: `An email with verification code has been sent to your email: ${newStudent.instiEmail}` + logger.info(`An email with verification code has been sent to your email: ${newStudent.instiEmail}`); + let status_code = 200; + return res.status(status_code).json({ + status_code: status_code, + message: `An email with verification code has been sent to your email: ${newStudent.instiEmail}`, + data: {} }); } catch(error){ - return res.status(400).json({ - status_code: 400, - message: error.toString() + logger.error(error.toString()); + let status_code = 500; + return res.status(status_code).json({ + status_code: status_code, + message: HttpStatus.getStatusText(status_code), + data: {} }); } } -exports.reSendInstiVerificationCode = async (req, res) => { +exports.verifyInstiEmail = async (req, res) => { try{ - if(!req.body.instiEmail || !validator.isEmail(req.body.instiEmail)){ - throw Error('Invalid parameters'); + if(!req.body.instiEmail || !validator.isEmail(req.body.instiEmail) || !req.body.verificationCode){ + logger.warn('Invalid parameters'); + let status_code = 400; + return res.status(status_code).json({ + status_code: status_code, + message: HttpStatus.getStatusText(status_code), + data: {} + }); + } + if(!registerUtil.checkEduEmail(req.body.instiEmail)){ + logger.warn(`Please enter a valid institute(.edu) email address : ${req.body.instiEmail}`); + let status_code = 400; + return res.status(status_code).json({ + status_code: status_code, + message: 'Please enter a valid institute(.edu) email address', + data: {} + }); } const email = req.session.student.email; const instiEmail = req.body.instiEmail; + const verificationCode = req.body.verificationCode; const student = await Student.findOne({ email: email, - instiEmail: instiEmail + instiEmail: instiEmail, + verificationCode: verificationCode, + isVerified2: false }).exec(); if(!student){ - throw Error('Sorry, this email is not registered.'); + logger.warn(`Please check your email: ${email} and verification code.`); + let status_code = 400; + return res.status(status_code).json({ + status_code: status_code, + message: 'Please check your email and verification code.', + data: {} + }); } - student.instiVerificationCode = randomstring.generate(6); - student.isVerified2 = false; + // Creating directory for the student + await mkdirp(path.join(config.directory.UPLOADS_DIR,student.applicationNumber)); - let mailResponse = await sendgridMailUtil.sendVerificationCode(student.instiEmail,student.instiVerificationCode); - if(mailResponse.status_code!=200){ - throw Error(mailResponse.message); - } + student.isVerified2 = true; + // Changing verification code after verification + student.verificationCode = randomstring.generate(6); + await student.save(); - let newStudent = await student.save(); - - return res.json({ - status_code: 200, - message: `An email with new verification code has been sent to your email: ${newStudent.instiEmail}` + logger.info(`Institute email successfully verified : ${student.instiEmail}`); + let status_code = 200; + return res.status(status_code).json({ + status_code: status_code, + message: HttpStatus.getStatusText(status_code), + data: {} }); } catch(error){ - return res.status(400).json({ - status_code: 400, - message: error.toString() + logger.error(error.toString()); + let status_code = 500; + return res.status(status_code).json({ + status_code: status_code, + message: HttpStatus.getStatusText(status_code), + data: {} }); } } -exports.verifyInstiEmail = async (req, res) => { +exports.forgotPassword = async (req, res) => { try{ - if(!req.body.instiEmail || !validator.isEmail(req.body.instiEmail) || !req.body.instiVerificationCode){ - throw Error('Invalid parameters'); + if(!req.body.email || !validator.isEmail(req.body.email)){ + logger.warn('Invalid parameters'); + let status_code = 400; + return res.status(status_code).json({ + status_code: status_code, + message: HttpStatus.getStatusText(status_code), + data: {} + }); } - - const email = req.session.student.email; - const instiEmail = req.body.instiEmail; - const instiVerificationCode = req.body.instiVerificationCode; - const student = await Student.findOne({ - email: email, - instiEmail: instiEmail, - instiVerificationCode: instiVerificationCode, - isVerified2: false - }).exec(); + const email = req.body.email; + let student = await Student.findOne({email: email}).exec(); if(!student){ - throw Error('Incorrect verification code.'); + logger.warn(`Email doesn't exists: ${email}`); + let status_code = 400; + return res.status(status_code).json({ + status_code: status_code, + message: `Email doesn't exists!`, + data: {} + }); + } + student.verificationCode = randomstring.generate(6); + + let mailResponse = await sendgridMailUtil.sendPasswordVerificationCode(student.email,student.verificationCode); + if(mailResponse.status_code!=200){ + throw Error(mailResponse.message); } - student.isVerified2 = true; await student.save(); - res.status(200).json({ - status_code: 200, - message: 'Success' + logger.info(`An email with verification code has been sent to email: ${student.email}`); + let status_code = 200; + return res.status(status_code).json({ + status_code: status_code, + message: `An email with verification code has been sent to your email: ${student.email}`, + data: {} }); } catch(error){ - res.status(400).json({ - status_code: 400, - message: error.toString() + logger.error(error.toString()); + let status_code = 500; + return res.status(status_code).json({ + status_code: status_code, + message: HttpStatus.getStatusText(status_code), + data: {} }); } } -exports.forgotPassword = async (req, res) => { +exports.resetPassword = async (req, res) => { + try{ + if(!req.body.email || !req.body.verificationCode || !req.body.password || !req.body.confirmPassword || !validator.isEmail(req.body.email)){ + logger.warn('Invalid parameters'); + let status_code = 400; + return res.status(status_code).json({ + status_code: status_code, + message: HttpStatus.getStatusText(status_code), + data: {} + }); + } + const email = req.body.email; + let student = await Student.findOne({email: email, verificationCode: req.body.verificationCode}).exec(); + if(!student){ + logger.warn(`Please check your email: ${email} and verification code.`); + let status_code = 400; + return res.status(status_code).json({ + status_code: status_code, + message: 'Please check your email and verification code.', + data: {} + }); + } -} + let password = req.body.password; + let confirmPassword = req.body.confirmPassword; + + if( !(password==confirmPassword) || (password.length<8) ){ + logger.warn(`Password Mismatch or Invalid password: ${email}`); + let status_code = 400; + return res.status(status_code).json({ + status_code: status_code, + message: 'Password Mismatch or Invalid password.', + data: {} + }); + } -exports.resetPassword = async (req, res) => { + if(!passwordUtil.checkPasswordRule(password)){ + logger.warn(`Password rule not passed for email: ${student.email}`); + let status_code = 400; + return res.status(status_code).json({ + status_code: status_code, + message: 'Password must be atleast 8 characters in length and should contain 1 lowercase letter, 1 uppercase letter, 1 number and 1 special character.', + data: {} + }); + } + password = await passwordUtil.hashPassword(password); + student.password = password; + // Changing verification code after changing password + student.verificationCode = randomstring.generate(6); + + await student.save(); + + logger.info(`Password successfully reset for email: ${student.email}`); + let status_code = 200; + return res.status(status_code).json({ + status_code: status_code, + message: `Password successfully reset.`, + data: {} + }); + } catch(error){ + logger.error(error.toString()); + let status_code = 500; + return res.status(status_code).json({ + status_code: status_code, + message: HttpStatus.getStatusText(status_code), + data: {} + }); + } } exports.registerStudent = async (req, res) => { try{ if(!registerUtil.checkEmptyInput(req)){ - throw Error('Invalid parameters'); + logger.warn('Invalid parameters'); + let status_code = 400; + return res.status(status_code).json({ + status_code: status_code, + message: HttpStatus.getStatusText(status_code), + data: {} + }); } let password = req.body.password; let confirmPassword = req.body.confirmPassword; - if( !(password==confirmPassword) || (password.length<8) ) - throw Error('Password Mismatch or Invalid password.'); + if( !(password==confirmPassword) || (password.length<8) ){ + logger.warn(`Password Mismatch or Invalid password: ${email}`); + let status_code = 400; + return res.status(status_code).json({ + status_code: status_code, + message: 'Password Mismatch or Invalid password.', + data: {} + }); + } + if(!passwordUtil.checkPasswordRule(password)){ + logger.warn(`Password rule not passed for email: ${student.email}`); + let status_code = 400; + return res.status(status_code).json({ + status_code: status_code, + message: 'Password must be atleast 8 characters in length and should contain 1 lowercase letter, 1 uppercase letter, 1 number and 1 special character.', + data: {} + }); + } + const student = await Student.findOne({ email: req.body.email, verificationCode: req.body.verificationCode, isVerified1: false }).exec(); if(!student){ - throw Error('Please check your email and verification code.'); + logger.warn(`Please check your email: ${email} and verification code.`); + let status_code = 400; + return res.status(status_code).json({ + status_code: status_code, + message: 'Please check your email and verification code.', + data: {} + }); } password = await passwordUtil.hashPassword(password); student.password = password; student.isVerified1 = true; + // Changing verification code after verification + student.verificationCode = randomstring.generate(6); student.personalInfo = { name: sanitize(req.body.name), rollNumber: sanitize(req.body.rollNumber), @@ -266,15 +422,21 @@ exports.registerStudent = async (req, res) => { let newStudent = await student.save(); - return res.json({ - status_code: 200, - message: `User with email: ${newStudent.email} successfully registered.` + logger.info(`User with email: ${newStudent.email} successfully registered.`); + let status_code = 200; + return res.status(status_code).json({ + status_code: status_code, + message: `User with email: ${newStudent.email} successfully registered.`, + data: {} }); } catch(error){ - return res.status(400).json({ - status_code: 400, - message: error.toString() + logger.error(error.toString()); + let status_code = 500; + return res.status(status_code).json({ + status_code: status_code, + message: HttpStatus.getStatusText(status_code), + data: {} }); } } @@ -282,7 +444,13 @@ exports.registerStudent = async (req, res) => { exports.loginStudent = async (req, res) => { try{ if(!req.body.email || !req.body.password || !validator.isEmail(req.body.email)){ - throw Error('Invalid Parameters'); + logger.warn('Invalid parameters'); + let status_code = 400; + return res.status(status_code).json({ + status_code: status_code, + message: HttpStatus.getStatusText(status_code), + data: {} + }); } const email = req.body.email; @@ -292,7 +460,13 @@ exports.loginStudent = async (req, res) => { isVerified1: true }).exec(); if(!student){ - throw Error('Please check your email, user account does not exists.'); + logger.warn(`Please check your email ${email}, user account does not exists.`); + let status_code = 400; + return res.status(status_code).json({ + status_code: status_code, + message: 'Please check your email, user account does not exists.', + data: {} + }); } let isAuthenticatedUser = await passwordUtil.comparePassword(password, student.password); @@ -301,22 +475,72 @@ exports.loginStudent = async (req, res) => { req.session.student = { email: student.email } - return res.status(200).json({ - status_code: 200, - message: 'Successfully, logged in to your account.' + + logger.info(`User with email: ${student.email} successfully logged in.`); + let status_code = 200; + return res.status(status_code).json({ + status_code: status_code, + message: 'Successfully, logged in to your account.', + data: {} }); } else{ - return res.status(400).json({ - status_code: 400, - message: 'Username or Password is incorrect.' + logger.warn(`Username or Password is incorrect for email: ${email}`); + let status_code = 400; + return res.status(status_code).json({ + status_code: status_code, + message: `Username or Password is incorrect`, + data: {} }); } } catch(error){ - return res.status(400).json({ - status_code: 400, - message: error.toString() + logger.error(error.toString()); + let status_code = 500; + return res.status(status_code).json({ + status_code: status_code, + message: HttpStatus.getStatusText(status_code), + data: {} + }); + } +} + +exports.checkSession = async (req, res) => { + try{ + if(!req.query.email || !validator.isEmail(req.query.email)){ + logger.warn('Invalid parameters'); + let status_code = 400; + return res.status(status_code).json({ + status_code: status_code, + message: HttpStatus.getStatusText(status_code), + data: {} + }); + } + + let isLoggedIn = false; + if (req.session.student && req.session.student.email) { + if(req.session.student.email == req.query.email){ + logger.info(`${req.session.student.email} student logged in`); + isLoggedIn = true; + } + } + + let status_code = 200; + return res.status(status_code).json({ + status_code: status_code, + message: HttpStatus.getStatusText(status_code), + data: { + isLoggedIn: isLoggedIn + } + }); + + } catch(error) { + logger.error(error.toString()); + let status_code = 500; + return res.status(status_code).json({ + status_code: status_code, + message: HttpStatus.getStatusText(status_code), + data: {} }); } } @@ -324,14 +548,21 @@ exports.loginStudent = async (req, res) => { exports.logoutStudent = async (req, res) => { try{ let logout = req.session.destroy(); - return res.status(200).json({ - status_code: 200, - message: 'Successfully, logged out of your account.' + + logger.info(`User successfully logged out.`); + let status_code = 200; + return res.status(status_code).json({ + status_code: status_code, + message: 'Successfully, logged out of your account.', + data: {} }); } catch(error){ - return res.status(400).json({ - status_code: 400, - message: error.toString() + logger.error(error.toString()); + let status_code = 500; + return res.status(status_code).json({ + status_code: status_code, + message: HttpStatus.getStatusText(status_code), + data: {} }); } } \ No newline at end of file diff --git a/src/controllers/certificates.controller.js b/src/controllers/certificates.controller.js index 74e25df..e508b27 100644 --- a/src/controllers/certificates.controller.js +++ b/src/controllers/certificates.controller.js @@ -1,17 +1,206 @@ +const HttpStatus = require('http-status-codes'); +const path = require('path'); +const randomstring = require('randomstring'); + +// Importing config/env variables +const logger = require('../config/winston'); + // Importing models const Student = require('../models/student.model'); -exports.uploadCertificate = async (req, res) => { +//Importing utils +const Uploader = require('../utils/upload.util'); + + +exports.uploadGradeSheetSem1 = async (req, res) => { try{ + const email = req.session.student.email; + let student = await Student.findOne({email: email}).exec(); + let fieldName = 'gradeSheetSem1'; + let fileName = student.applicationNumber + '_' + fieldName + '_' + randomstring.generate(4); + const fileOptions = { + fileName: fileName, + fieldName: fieldName, + allowedFileSize : 1000000, // 1 MB + allowedFileTypesRE: /jpeg|jpg|png/, + } + let fileUploader = new Uploader(req,student.applicationNumber,fileOptions); + let uploadResponse = await fileUploader.uploadSingle(req,res,fieldName); + + if(uploadResponse.status_code!=200){ + logger.warn(uploadResponse.message); + return res.status(uploadResponse.status_code).json({ + status_code: uploadResponse.status_code, + message: uploadResponse.message, + data: {} + }); + + } + let location = path.join('uploads',student.applicationNumber,uploadResponse.data.file.filename); + student.certificates.gradeSheetSem1 = location; + await student.save(); + logger.info(`Successfully uploaded GradeSheetSem1 for email: ${student.email}`); - return res.status(200).json({ - status_code: 200, - message: `Successfully uploaded file.` + return res.status(uploadResponse.status_code).json({ + status_code: uploadResponse.status_code, + message: uploadResponse.message, + data: { + filePath: student.certificates.gradeSheetSem1 + } }); + } catch(error){ - return res.status(400).json({ - status_code: 400, - message: error.toString() + logger.error(error.toString()); + let status_code = 500; + return res.status(status_code).json({ + status_code: status_code, + message: HttpStatus.getStatusText(status_code), + data: {} + }); + } +} + +exports.uploadInstiCertificate = async (req, res) => { + try{ + const email = req.session.student.email; + let student = await Student.findOne({email: email}).exec(); + let fieldName = 'instiCertificate'; + let fileName = student.applicationNumber + '_' + fieldName + '_' + randomstring.generate(4); + const fileOptions = { + fileName: fileName, + fieldName: fieldName, + allowedFileSize : 1000000, // 1 MB + allowedFileTypesRE: /jpeg|jpg|png/, + } + let fileUploader = new Uploader(req,student.applicationNumber,fileOptions); + let uploadResponse = await fileUploader.uploadSingle(req,res,fieldName); + + if(uploadResponse.status_code!=200){ + logger.warn(uploadResponse.message); + return res.status(uploadResponse.status_code).json({ + status_code: uploadResponse.status_code, + message: uploadResponse.message, + data: {} + }); + } + + let location = path.join('uploads',student.applicationNumber,uploadResponse.data.file.filename); + student.certificates.instiCertificate = location; + await student.save(); + logger.info(`Successfully uploaded institute certificate for email: ${student.email}`); + + + return res.status(uploadResponse.status_code).json({ + status_code: uploadResponse.status_code, + message: uploadResponse.message, + data: { + filePath: student.certificates.instiCertificate + } + }); + + } catch(error){ + logger.error(error.toString()); + let status_code = 500; + return res.status(status_code).json({ + status_code: status_code, + message: HttpStatus.getStatusText(status_code), + data: {} + }); + } +} + +exports.uploadNonInstiCertificate = async (req, res) => { + try{ + const email = req.session.student.email; + let student = await Student.findOne({email: email}).exec(); + let fieldName = 'nonInstiCertificate'; + let fileName = student.applicationNumber + '_' + fieldName + '_' + randomstring.generate(4); + const fileOptions = { + fileName: fileName, + fieldName: fieldName, + allowedFileSize : 1000000, // 1 MB + allowedFileTypesRE: /jpeg|jpg|png/, + } + let fileUploader = new Uploader(req,student.applicationNumber,fileOptions); + let uploadResponse = await fileUploader.uploadSingle(req,res,fieldName); + + if(uploadResponse.status_code!=200){ + logger.warn(uploadResponse.message); + return res.status(uploadResponse.status_code).json({ + status_code: uploadResponse.status_code, + message: uploadResponse.message, + data: {} + }); + } + + let location = path.join('uploads',student.applicationNumber,uploadResponse.data.file.filename); + student.certificates.nonInstiCertificate = location; + await student.save(); + logger.info(`Successfully uploaded non-institute certificate for email: ${student.email}`); + + return res.status(uploadResponse.status_code).json({ + status_code: uploadResponse.status_code, + message: uploadResponse.message, + data: { + filePath: student.certificates.nonInstiCertificate + } + }); + + } catch(error){ + logger.error(error.toString()); + let status_code = 500; + return res.status(status_code).json({ + status_code: status_code, + message: HttpStatus.getStatusText(status_code), + data: {} + }); + } +} + +exports.uploadGradeSheetMOOC = async (req, res) => { + try{ + const email = req.session.student.email; + let student = await Student.findOne({email: email}).exec(); + let fieldName = 'gradeSheetMOOC'; + let fileName = student.applicationNumber + '_' + fieldName + '_' + randomstring.generate(4); + const fileOptions = { + fileName: fileName, + fieldName: fieldName, + allowedFileSize : 1000000, // 1 MB + allowedFileTypesRE: /jpeg|jpg|png/, + } + let fileUploader = new Uploader(req,student.applicationNumber,fileOptions); + let uploadResponse = await fileUploader.uploadSingle(req,res,fieldName); + + if(uploadResponse.status_code!=200){ + logger.warn(uploadResponse.message); + return res.status(uploadResponse.status_code).json({ + status_code: uploadResponse.status_code, + message: uploadResponse.message, + data: {} + }); + } + + let location = path.join('uploads',student.applicationNumber,uploadResponse.data.file.filename); + student.certificates.gradeSheetMOOC = location; + await student.save(); + logger.info(`Successfully uploaded GradeSheetMOOC for email: ${student.email}`); + + return res.status(uploadResponse.status_code).json({ + status_code: uploadResponse.status_code, + message: uploadResponse.message, + data: { + filePath: student.certificates.gradeSheetMOOC + } + }); + + } catch(error){ + logger.error(error.toString()); + let status_code = 500; + return res.status(status_code).json({ + status_code: status_code, + message: HttpStatus.getStatusText(status_code), + data: {} }); } } @@ -19,15 +208,25 @@ exports.uploadCertificate = async (req, res) => { exports.viewCertificates = async (req, res) => { try{ const email = req.session.student.email; - const student = await Student.findOne({email: email}).select('certificates -_id').exec(); - return res.status(200).json({ - status_code: 200, - message: student.certificates + const student = await Student.findOne({email: email}).select('certificates applicationNumber -_id').exec(); + + logger.info(`Successfully retrieved certificate details for email: ${student.email}`); + let status_code = 200; + return res.status(status_code).json({ + status_code: status_code, + message: HttpStatus.getStatusText(status_code), + data: { + applicationNumber: student.applicationNumber, + certificates: student.certificates + } }); } catch(error){ - return res.status(400).json({ - status_code: 400, - message: error.toString() + logger.error(error.toString()); + let status_code = 500; + return res.status(status_code).json({ + status_code: status_code, + message: HttpStatus.getStatusText(status_code), + data: {} }); } } \ No newline at end of file diff --git a/src/controllers/essays.controller.js b/src/controllers/essays.controller.js index bf8018b..ad24b59 100644 --- a/src/controllers/essays.controller.js +++ b/src/controllers/essays.controller.js @@ -1,30 +1,316 @@ +const HttpStatus = require('http-status-codes'); +const path = require('path'); +const sanitize = require('mongo-sanitize'); +const randomstring = require('randomstring'); + +// Importing config/env variables +const logger = require('../config/winston'); + // Importing models const Student = require('../models/student.model'); -exports.uploadSOPToMentors = async (req, res, next) => { - -} +//Importing utils +const mentorsUtil = require('../utils/mentors.util'); +const Uploader = require('../utils/upload.util'); + -exports.uploadCommunityToMentors = async (req, res, next) => { +exports.sendSOPToMentors = async (req, res) => { + try{ + if(!req.body.sopLink){ + logger.warn('Invalid parameters'); + let status_code = 400; + return res.status(status_code).json({ + status_code: status_code, + message: HttpStatus.getStatusText(status_code), + data: {} + }); + } + const email = req.session.student.email; + const sopLink = sanitize(req.body.sopLink); + let student = await Student.findOne({email: email}).exec(); + student.essays.mentors.sop = sopLink; + let submissionType = 'Statement of purpose'; + let mailJobs = await mentorsUtil.createMailJobs(student.personalInfo.name, sopLink, submissionType); + if(mailJobs.status_code!=200){ + throw(mailJobs.message); + } + + await student.save(); + + logger.info(`Successfully created mailing jobs and saved SOP link for email: ${student.email}`); + let status_code = 200; + return res.status(status_code).json({ + status_code: status_code, + message: 'Successfully sent to mentors!', + data: {} + }); + + } catch(error){ + logger.error(error.toString()); + let status_code = 500; + return res.status(status_code).json({ + status_code: status_code, + message: HttpStatus.getStatusText(status_code), + data: {} + }); + } } -exports.uploadSocietyToMentors = async (req, res, next) => { +exports.sendCommunityToMentors = async (req, res) => { + try{ + if(!req.body.communityLink){ + logger.warn('Invalid parameters'); + let status_code = 400; + return res.status(status_code).json({ + status_code: status_code, + message: HttpStatus.getStatusText(status_code), + data: {} + }); + } + const email = req.session.student.email; + const communityLink = sanitize(req.body.communityLink); + let student = await Student.findOne({email: email}).exec(); + student.essays.mentors.community = communityLink; + + let submissionType = `'For the community' essay`; + let mailJobs = await mentorsUtil.createMailJobs(student.personalInfo.name, communityLink, submissionType); + if(mailJobs.status_code!=200){ + throw(mailJobs.message); + } + + await student.save(); + + logger.info(`Successfully created mailing jobs and saved community essay link for email: ${student.email}`); + let status_code = 200; + return res.status(status_code).json({ + status_code: status_code, + message: 'Successfully sent to mentors!', + data: {} + }); + + } catch(error){ + logger.error(error.toString()); + let status_code = 500; + return res.status(status_code).json({ + status_code: status_code, + message: HttpStatus.getStatusText(status_code), + data: {} + }); + } } -exports.uploadSOPFinal = async (req, res, next) => { +exports.sendSocietyToMentors = async (req, res) => { + try{ + if(!req.body.societyLink){ + logger.warn('Invalid parameters'); + let status_code = 400; + return res.status(status_code).json({ + status_code: status_code, + message: HttpStatus.getStatusText(status_code), + data: {} + }); + } + + const email = req.session.student.email; + const societyLink = sanitize(req.body.societyLink); + let student = await Student.findOne({email: email}).exec(); + student.essays.mentors.society = societyLink; + + let submissionType = `'For the society' essay`; + let mailJobs = await mentorsUtil.createMailJobs(student.personalInfo.name, societyLink, submissionType); + if(mailJobs.status_code!=200){ + throw(mailJobs.message); + } + + await student.save(); + + logger.info(`Successfully created mailing jobs and saved society essay link for email: ${student.email}`); + let status_code = 200; + return res.status(status_code).json({ + status_code: status_code, + message: 'Successfully sent to mentors!', + data: {} + }); + } catch(error){ + logger.error(error.toString()); + let status_code = 500; + return res.status(status_code).json({ + status_code: status_code, + message: HttpStatus.getStatusText(status_code), + data: {} + }); + } } -exports.uploadCommunityFinal = async (req, res, next) => { +exports.uploadFinalSOP = async (req, res) => { + try{ + const email = req.session.student.email; + let student = await Student.findOne({email: email}).exec(); + let fieldName = 'finalSOP'; + let fileName = student.applicationNumber + '_' + fieldName + '_' + randomstring.generate(4); + const fileOptions = { + fileName: fileName, + fieldName: fieldName, + allowedFileSize : 3000000, // 3 MB + allowedFileTypesRE: /pdf/, + } + let fileUploader = new Uploader(req,student.applicationNumber,fileOptions); + let uploadResponse = await fileUploader.uploadSingle(req,res,fieldName); + if(uploadResponse.status_code!=200){ + logger.warn(uploadResponse.message); + return res.status(uploadResponse.status_code).json({ + status_code: uploadResponse.status_code, + message: uploadResponse.message, + data: {} + }); + } + + let location = path.join('uploads',student.applicationNumber,uploadResponse.data.file.filename); + student.essays.final.sop = location; + await student.save(); + logger.info(`Successfully uploaded final SOP for email: ${student.email}`); + + return res.status(uploadResponse.status_code).json({ + status_code: uploadResponse.status_code, + message: uploadResponse.message, + data: { + filePath: student.essays.final.sop + } + }); + + } catch(error){ + logger.error(error.toString()); + let status_code = 500; + return res.status(status_code).json({ + status_code: status_code, + message: HttpStatus.getStatusText(status_code), + data: {} + }); + } } -exports.uploadSocietyFinal = async (req, res, next) => { +exports.uploadFinalCommunity = async (req, res) => { + try{ + const email = req.session.student.email; + let student = await Student.findOne({email: email}).exec(); + let fieldName = 'finalCommunity'; + let fileName = student.applicationNumber + '_' + fieldName + '_' + randomstring.generate(4); + const fileOptions = { + fileName: fileName, + fieldName: fieldName, + allowedFileSize : 3000000, // 3 MB + allowedFileTypesRE: /pdf/, + } + let fileUploader = new Uploader(req,student.applicationNumber,fileOptions); + let uploadResponse = await fileUploader.uploadSingle(req,res,fieldName); + + if(uploadResponse.status_code!=200){ + logger.warn(uploadResponse.message); + return res.status(uploadResponse.status_code).json({ + status_code: uploadResponse.status_code, + message: uploadResponse.message, + data: {} + }); + } + + let location = path.join('uploads',student.applicationNumber,uploadResponse.data.file.filename); + student.essays.final.community = location; + await student.save(); + logger.info(`Successfully uploaded final community essay for email: ${student.email}`); + + return res.status(uploadResponse.status_code).json({ + status_code: uploadResponse.status_code, + message: uploadResponse.message, + data: { + filePath: student.essays.final.community + } + }); + } catch(error){ + logger.error(error.toString()); + let status_code = 500; + return res.status(status_code).json({ + status_code: status_code, + message: HttpStatus.getStatusText(status_code), + data: {} + }); + } } -exports.viewFinalEssays = async (req, res, next) => { +exports.uploadFinalSociety = async (req, res) => { + try{ + const email = req.session.student.email; + let student = await Student.findOne({email: email}).exec(); + let fieldName = 'finalSociety'; + let fileName = student.applicationNumber + '_' + fieldName + '_' + randomstring.generate(4); + const fileOptions = { + fileName: fileName, + fieldName: fieldName, + allowedFileSize : 3000000, // 3 MB + allowedFileTypesRE: /pdf/, + } + let fileUploader = new Uploader(req,student.applicationNumber,fileOptions); + let uploadResponse = await fileUploader.uploadSingle(req,res,fieldName); + + if(uploadResponse.status_code!=200){ + logger.warn(uploadResponse.message); + return res.status(uploadResponse.status_code).json({ + status_code: uploadResponse.status_code, + message: uploadResponse.message, + data: {} + }); + } + + let location = path.join('uploads',student.applicationNumber,uploadResponse.data.file.filename); + student.essays.final.society = location; + await student.save(); + logger.info(`Successfully uploaded final society essay for email: ${student.email}`); + + return res.status(uploadResponse.status_code).json({ + status_code: uploadResponse.status_code, + message: uploadResponse.message, + data: { + filePath: student.essays.final.society + } + }); + + } catch(error){ + logger.error(error.toString()); + let status_code = 500; + return res.status(status_code).json({ + status_code: status_code, + message: HttpStatus.getStatusText(status_code), + data: {} + }); + } +} +exports.viewFinalEssays = async (req, res) => { + try{ + const email = req.session.student.email; + const student = await Student.findOne({email: email}).select('essays applicationNumber -_id').exec(); + + logger.info(`Successfully retrieved final essay links/details for email: ${student.email}`); + let status_code = 200; + return res.status(status_code).json({ + status_code: status_code, + message: HttpStatus.getStatusText(status_code), + data: { + applicationNumber: student.applicationNumber, + essays: student.essays + } + }); + } catch(error){ + logger.error(error.toString()); + let status_code = 500; + return res.status(status_code).json({ + status_code: status_code, + message: HttpStatus.getStatusText(status_code), + data: {} + }); + } } \ No newline at end of file diff --git a/src/controllers/form.controller.js b/src/controllers/form.controller.js index f2a8206..18e8670 100644 --- a/src/controllers/form.controller.js +++ b/src/controllers/form.controller.js @@ -1,29 +1,307 @@ +const HttpStatus = require('http-status-codes'); +const kue = require('kue'); +const path = require('path'); +const randomstring = require('randomstring'); + +// Importing config/env variables +const config = require('../config/config'); +const logger = require('../config/winston'); + // Importing models const Student = require('../models/student.model'); // Importing utils const formUtil = require('../utils/form.util'); +const pdfUtil = require('../utils/pdf.util'); + +// Connections +const queue = kue.createQueue({ + prefix: 'q', + redis: { + port: config.redis.PORT, + host: config.redis.HOST + } +}); + + +exports.formAccess = async (req, res) => { + try { + const email = req.session.student.email; + let student = await Student.findOne({email: email}).exec(); + + let access = false; + if(student.isVerified1 && student.isVerified2){ + access = true; + } + + logger.info(`Form access for ${email}: ${access}`); + let status_code = 200; + return res.status(status_code).json({ + status_code: status_code, + message: HttpStatus.getStatusText(status_code), + data: { + access: access + } + }); + } catch(error){ + logger.error(error.toString()); + let status_code = 500; + return res.status(status_code).json({ + status_code: status_code, + message: HttpStatus.getStatusText(status_code), + data: {} + }); + } +} + +exports.getApplicationNumber = async (req, res) => { + try { + const email = req.session.student.email; + let student = await Student.findOne({email: email}).exec(); + + let applicationNumber = student.applicationNumber; + + logger.info(`Application Number for ${email}: ${applicationNumber}`); + let status_code = 200; + return res.status(status_code).json({ + status_code: status_code, + message: HttpStatus.getStatusText(status_code), + data: { + applicationNumber: applicationNumber + } + }); + } catch(error){ + logger.error(error.toString()); + let status_code = 500; + return res.status(status_code).json({ + status_code: status_code, + message: HttpStatus.getStatusText(status_code), + data: {} + }); + } +} -exports.previewForm = async (req, res, next) => { +exports.getFormStatus = async (req, res) => { + try { + const email = req.session.student.email; + let student = await Student.findOne({email: email}).exec(); + logger.info(`Successfully, retrieved application form status.`); + let status_code = 200; + return res.status(status_code).json({ + status_code: status_code, + message: HttpStatus.getStatusText(status_code), + data: { + applicationNumber: student.applicationNumber, + submissionStatus: student.isSubmitted + } + }); + } catch(error){ + logger.error(error.toString()); + let status_code = 500; + return res.status(status_code).json({ + status_code: status_code, + message: HttpStatus.getStatusText(status_code), + data: {} + }); + } } -exports.submitForm = async (req, res, next) => { +exports.generateApplication = async (req, res) => { try{ const email = req.session.student.email; let student = await Student.findOne({email: email}).exec(); - if(!formUtil.checkFormSubmission(student)) - throw('Please fill all required fields to submit form.'); + + if(!formUtil.checkFormSubmission(student)){ + logger.warn('Please fill all required fields to submit form.'); + let status_code = 400; + return res.status(status_code).json({ + status_code: status_code, + message: `Please fill all required fields to submit form.`, + data: {} + }); + } + + // Add file paths to generate personal information pdf + const ejsPath = path.join(config.directory.SRC_DIR,'templates','pdfs','personalInfo.ejs'); + const personalInfoFileName = student.applicationNumber + '_personalInfo_' + randomstring.generate(4) + '_' + Date.now() + '.pdf'; + const destinationFilePath = path.join(config.directory.UPLOADS_DIR,student.applicationNumber,personalInfoFileName); + + // Generate pdf with personal information, certificates and grade sheets + let genPdfResponse = await pdfUtil.generatePdf(student,ejsPath,destinationFilePath); + if(genPdfResponse.status_code!=200){ + logger.error(genPdfResponse.message.toString()); + return res.status(genPdfResponse.status_code).json({ + status_code: genPdfResponse.status_code, + message: genPdfResponse.message, + data: {} + }); + } + logger.info(`Generated personal info pdf for email: ${student.email}.`); + + const personalInfoFilePath = path.join('uploads',student.applicationNumber,personalInfoFileName); + student.personalInfo.filePath = personalInfoFilePath; + student = await student.save(); + + // Add file paths to merge all pdfs to create student application + const filesResponse = await formUtil.returnFilesLocationAsArray(student); + if(filesResponse.status_code!=200){ + return res.status(filesResponse.status_code).json({ + status_code: filesResponse.status_code, + message: filesResponse.message, + data: {} + }); + } + + const applicationFileName = student.applicationNumber + '_application_' + randomstring.generate(4) + '_' + Date.now() + '.pdf'; + const applicationRelativePath = path.join('uploads',student.applicationNumber,applicationFileName); + const applicationDestinationPath = path.join(config.directory.UPLOADS_DIR,student.applicationNumber,applicationFileName); + + // Merge all pdfs to create student application + const mergeResponse = await pdfUtil.mergePdf(filesResponse.data.filesPathArray,applicationDestinationPath); + + if(mergeResponse.status_code!=200){ + return res.status(mergeResponse.status_code).json({ + status_code: mergeResponse.status_code, + message: mergeResponse.message, + data: {} + }); + } + student.applicationFilePath = applicationRelativePath; + let newStudent = await student.save(); + + logger.info(`Generated merged pdf containing all submitted files for email: ${student.email}.`); + let status_code = 200; + return res.status(status_code).json({ + status_code: status_code, + message: HttpStatus.getStatusText(status_code), + data: { + applicationFilePath: newStudent.applicationFilePath + } + }); + + } catch(error){ + logger.error(error.toString()); + let status_code = 500; + return res.status(status_code).json({ + status_code: status_code, + message: HttpStatus.getStatusText(status_code), + data: {} + }); + } +} + +exports.viewApplication = async (req, res) => { + try { + const email = req.session.student.email; + let student = await Student.findOne({email: email}).exec(); + + if(!student.isSubmitted){ + logger.info('Forbidden: Application can only be viewed after form submission.'); + let status_code = 403; + return res.status(status_code).json({ + status_code: status_code, + message: `Application can only be viewed after form submission.`, + data: {} + }); + } + + logger.info(`Successfully, retrieved application form location.`); + let status_code = 200; + return res.status(status_code).json({ + status_code: status_code, + message: HttpStatus.getStatusText(status_code), + data: { + applicationNumber: student.applicationNumber, + applicationFilePath: student.applicationFilePath + } + }); + } catch(error){ + logger.error(error.toString()); + let status_code = 500; + return res.status(status_code).json({ + status_code: status_code, + message: HttpStatus.getStatusText(status_code), + data: {} + }); + } +} + +exports.submitForm = async (req, res) => { + try{ + const email = req.session.student.email; + let student = await Student.findOne({email: email}).exec(); + + if(!formUtil.checkFormSubmission(student)){ + logger.warn('Please fill all required fields to submit form.'); + let status_code = 400; + return res.status(status_code).json({ + status_code: status_code, + message: `Please fill all required fields to submit form.`, + data: {} + }); + } + + if(student.isSubmitted){ + logger.warn('Already submitted form.'); + let status_code = 400; + return res.status(status_code).json({ + status_code: status_code, + message: `Already submitted form.`, + data: {} + }); + } + + let summaryArchiveFileName = student.applicationNumber + '_summary_' + Date.now() + '.' + config.files.archive.format; + let summaryArchiveFilePath = path.join('uploads',student.applicationNumber,summaryArchiveFileName); + + let finalArchiveFileName = student.applicationNumber + '_final_' + Date.now() + '.' + config.files.archive.format; + let finalArchiveFilePath = path.join('uploads',student.applicationNumber,finalArchiveFileName); + + student.archive.summary = summaryArchiveFilePath; + student.archive.final = finalArchiveFilePath; + await student.save(); + + student = await Student.findOne({email: email}).select('-verificationCode -isVerified1 -isVerified2 -password -createdAt -updatedAt').exec(); + + // Archive summary application and attach to mail + let summaryArchiveJob = await queue.create('archiveSummary', { + student: JSON.stringify(student.toJSON()) + }) + .removeOnComplete(true) + .attempts(5) + .backoff({type: 'exponential'}) + .save(); + summaryArchiveJob.log(`${student.email}: Archive summary application job added to job queue.`); + logger.info(`${student.email}: Archive summary application job added to job queue.`); + + // Archive final application and upload to google drive + let finalArchiveJob = await queue.create('archiveFinal', { + student: JSON.stringify(student.toJSON()) + }) + .removeOnComplete(true) + .attempts(5) + .backoff({type: 'exponential'}) + .save(); + finalArchiveJob.log(`${student.email}: Archive final files job added to queue.`); + logger.info(`${student.email}: Archive final files job added to queue.`); + student.isSubmitted = true; await student.save(); - res.status(200).json({ + + logger.info(`Successfully submitted form for email: ${student.email}.`); + return res.status(200).json({ status_code: 200, - message: `Successfully submitted form.` + message: `Successfully submitted form.`, + data: {} }); } catch(error){ - res.status(400).json({ - status_code: 400, - message: error.toString() + logger.error(error.toString()); + let status_code = 500; + return res.status(status_code).json({ + status_code: status_code, + message: HttpStatus.getStatusText(status_code), + data: {} }); } } diff --git a/src/controllers/personalInfo.controller.js b/src/controllers/personalInfo.controller.js index f62cd93..60437ab 100644 --- a/src/controllers/personalInfo.controller.js +++ b/src/controllers/personalInfo.controller.js @@ -1,26 +1,44 @@ +const HttpStatus = require('http-status-codes'); const sanitize = require('mongo-sanitize'); +// Importing config/env variables +const logger = require('../config/winston'); + // Importing models const Student = require('../models/student.model'); exports.updateInfo = async (req, res) => { try{ - if(!req.body.introduction) - throw('Invalid parameters'); const email = req.session.student.email; - let introduction = sanitize(req.body.introduction); - introduction = introduction.substr(0,1000); - const student = await Student.findOne({email: email}).exec(); - student.personalInfo.introduction = introduction; + let student = await Student.findOne({email: email}).exec(); + + student.personalInfo.introduction = sanitize(req.body.introduction.substr(0,1000)); + student.personalInfo.gender = sanitize(req.body.gender); + student.personalInfo.disability.status = sanitize(req.body.disabilityStatus); + if(req.body.disabilityStatus && req.body.disabilityStatus=='Yes'){ + student.personalInfo.disability.description = sanitize(req.body.disabilityDescription.substr(0,100)); + } + student.personalInfo.nationality = sanitize(req.body.nationality); + student.personalInfo.firstGenStudent = sanitize(req.body.firstGenStudent); + student.personalInfo.refugee = sanitize(req.body.refugee); + student.personalInfo.pronoun = sanitize(req.body.pronoun); + await student.save(); - res.status(200).json({ - status_code: 200, - message: `Successfully updated student's introduction` + + logger.info(`Successfully updated student's personal information for email: ${student.email}`); + let status_code = 200; + return res.status(status_code).json({ + status_code: status_code, + message: `Successfully updated student's personal information`, + data: {} }); } catch(error){ - res.status(400).json({ - status_code: 400, - message: error.toString() + logger.error(error.toString()); + let status_code = 500; + return res.status(status_code).json({ + status_code: status_code, + message: HttpStatus.getStatusText(status_code), + data: {} }); } } @@ -29,17 +47,24 @@ exports.getInfo = async (req, res) => { try{ const email = req.session.student.email; const student = await Student.findOne({email: email}).select('personalInfo applicationNumber -_id').exec(); - res.status(200).json({ - status_code: 200, - message: { + + logger.info(`Successfully retrieved personal info for email: ${student.email}.`); + let status_code = 200; + return res.status(status_code).json({ + status_code: status_code, + message: HttpStatus.getStatusText(status_code), + data: { applicationNumber: student.applicationNumber, personalInfo: student.personalInfo } }); } catch(error){ - res.status(400).json({ - status_code: 400, - message: error.toString() + logger.error(error.toString()); + let status_code = 500; + return res.status(status_code).json({ + status_code: status_code, + message: HttpStatus.getStatusText(status_code), + data: {} }); } } diff --git a/src/controllers/requirements.controller.js b/src/controllers/requirements.controller.js index afe4dbc..1f6f280 100644 --- a/src/controllers/requirements.controller.js +++ b/src/controllers/requirements.controller.js @@ -1,39 +1,58 @@ +const HttpStatus = require('http-status-codes'); + +// Importing config/env variables +const logger = require('../config/winston'); + // Importing models const Student = require('../models/student.model'); // Importing utils const requirementsUtil = require('../utils/requirements.util'); -exports.getAllPendingRequirements = async (req, res, next) => { +exports.getAllPendingRequirements = async (req, res) => { try{ const email = req.session.student.email; const student = await Student.findOne({email: email}).exec(); const allPendingRequirements = requirementsUtil.getAllPendingRequirements(student); - res.status(200).json({ - status_code: 200, - message: allPendingRequirements + + logger.info(`Successfully retrieved all pending requirements for email: ${student.email}.`); + let status_code = 200; + return res.status(status_code).json({ + status_code: status_code, + message: HttpStatus.getStatusText(status_code), + data: allPendingRequirements }); } catch(error){ - res.status(400).json({ - status_code: 400, - message: error.toString() + logger.error(error.toString()); + let status_code = 500; + return res.status(status_code).json({ + status_code: status_code, + message: HttpStatus.getStatusText(status_code), + data: {} }); } } -exports.getNecessaryPendingRequirements = async (req, res, next) => { +exports.getNecessaryPendingRequirements = async (req, res) => { try{ const email = req.session.student.email; const student = await Student.findOne({email: email}).exec(); const necessaryPendingRequirements = requirementsUtil.getNecessaryPendingRequirements(student); - res.status(200).json({ - status_code: 200, - message: necessaryPendingRequirements + + logger.info(`Successfully retrieved all pending requirements for email: ${student.email}.`); + let status_code = 200; + return res.status(status_code).json({ + status_code: status_code, + message: HttpStatus.getStatusText(status_code), + data: necessaryPendingRequirements }); } catch(error){ - res.status(400).json({ - status_code: 400, - message: error.toString() + logger.error(error.toString()); + let status_code = 500; + return res.status(status_code).json({ + status_code: status_code, + message: HttpStatus.getStatusText(status_code), + data: {} }); } } diff --git a/src/controllers/signature.controller.js b/src/controllers/signature.controller.js index 74d546c..87e291b 100644 --- a/src/controllers/signature.controller.js +++ b/src/controllers/signature.controller.js @@ -1,25 +1,45 @@ +const HttpStatus = require('http-status-codes'); + const sanitize = require('mongo-sanitize'); const validator = require('validator'); +// Importing config/env variables +const logger = require('../config/winston'); + // Importing models const Student = require('../models/student.model'); exports.updateSignature = async (req, res, next) => { try{ - if(!req.body.signature || validator.isEmpty(req.body.signature)) - throw('Invalid Parameters'); + if(!req.body.signature || validator.isEmpty(req.body.signature)){ + logger.warn('Invalid parameters'); + let status_code = 400; + return res.status(status_code).json({ + status_code: status_code, + message: HttpStatus.getStatusText(status_code), + data: {} + }); + } + const email = req.session.student.email; let student = await Student.findOne({email: email}).exec(); student.signature = sanitize(req.body.signature); await student.save(); - return res.status(200).json({ - status_code: 200, - message: `Successfully updated signature.` + + logger.info(`Successfully updated signature for email: ${student.email}.`); + let status_code = 200; + return res.status(status_code).json({ + status_code: status_code, + message: `Successfully updated signature.`, + data: {} }); } catch(error){ - return res.status(400).json({ - status_code: 400, - message: error.toString() + logger.error(error.toString()); + let status_code = 500; + return res.status(status_code).json({ + status_code: status_code, + message: HttpStatus.getStatusText(status_code), + data: {} }); } } @@ -27,15 +47,25 @@ exports.updateSignature = async (req, res, next) => { exports.getSignature = async (req, res, next) => { try{ const email = req.session.student.email; - let student = await Student.findOne({email: email}).select('signature -_id').exec(); - return res.status(200).json({ - status_code: 200, - message: student + let student = await Student.findOne({email: email}).select('signature applicationNumber -_id').exec(); + + logger.info(`Successfully retrieved signature for email: ${student.email}.`); + let status_code = 200; + return res.status(status_code).json({ + status_code: status_code, + message: HttpStatus.getStatusText(status_code), + data: { + applicationNumber: student.applicationNumber, + signature: student.signature + } }); } catch(error){ - return res.status(400).json({ - status_code: 400, - message: error.toString() + logger.error(error.toString()); + let status_code = 500; + return res.status(status_code).json({ + status_code: status_code, + message: HttpStatus.getStatusText(status_code), + data: {} }); } } \ No newline at end of file diff --git a/src/kue.server.js b/src/kue.server.js new file mode 100644 index 0000000..dea8c8b --- /dev/null +++ b/src/kue.server.js @@ -0,0 +1,57 @@ +const express = require('express'); +const kue = require('kue'); +const ui = require('kue-ui'); +const signale = require('signale'); + +const app = express(); + +// Importing configuration file +const config = require('./config/config'); + +// Connections +const queue = kue.createQueue({ + prefix: 'q', + redis: { + port: config.redis.PORT, + host: config.redis.HOST + } +}); + +// env/config variables +const KUE_PORT = config.ports.KUE_PORT || 5000; + +ui.setup({ + apiURL: '/api', + baseURL: '/kue', + updateInterval: 5000 // Optional: Fetches new data every 5000 ms +}); + + +// Mount kue JSON api +app.use('/api', kue.app); + +// Mount UI +app.use('/kue', ui.app); + +// Route error handler +app.use( (err, req, res, next) => { + // Log errors + logger.error(`${err.status || 500} - ${err.message} - ${req.originalUrl} - ${req.method} - ${req.ip}`); +}); + +app.all('*', (req, res) => { + signale.error('Returning a 404 from the kue server catch-all route'); + return res.sendStatus(404); +}); + +exports.start = () => { + app.listen(KUE_PORT, () => { + signale.success(`Kue server listening on port: ${KUE_PORT}`); + }); +} + +exports.stop = () => { + app.close(KUE_PORT, () => { + signale.success(`Kue server shut down on port: ${KUE_PORT}`); + }); +} \ No newline at end of file diff --git a/src/middlewares/auth/student.middleware.js b/src/middlewares/auth/student.middleware.js index acb7162..41735ba 100644 --- a/src/middlewares/auth/student.middleware.js +++ b/src/middlewares/auth/student.middleware.js @@ -1,5 +1,9 @@ +const HttpStatus = require('http-status-codes'); const validator = require('validator'); +// Importing config/env variables +const logger = require('../../config/winston'); + // Importing models const Student = require('../../models/student.model'); @@ -8,36 +12,63 @@ exports.checkStudentSession = (req, res, next) => { if (req.session.student && req.session.student.email) { return next(); } - return res.status(400).json({ - status_code: 400, - message: 'Unauthorised access' + + logger.info('Unauthorised access'); + let status_code = 401; + return res.status(status_code).json({ + status_code: status_code, + message: 'Unauthorised access', + data: {} }); } catch(error) { - return res.status(400).json({ - status_code: 400, - message: error.toString() + logger.error(error.toString()); + let status_code = 500; + return res.status(status_code).json({ + status_code: status_code, + message: HttpStatus.getStatusText(status_code), + data: {} }); } } exports.checkAccountInactivation = async(req, res, next) => { try { - if(!req.body.email || !validator.isEmail(req.body.email)){ - throw Error('Invalid parameters'); + + let email = null; + + if(req.method=='GET') + email = req.query.email; + else if(req.method=='POST') + email = req.body.email; + + if(!email || !validator.isEmail(email)){ + logger.warn('Invalid parameters'); + let status_code = 401; + return res.status(status_code).json({ + status_code: status_code, + message: HttpStatus.getStatusText(status_code), + data: {} + }); } - const email = req.body.email; let student = await Student.findOne({email: email}).exec(); if(!student || !student.isVerified1){ return next(); } - return res.status(400).json({ - status_code: 400, - message: 'Primary email is already verified.' + + logger.info(`Primary email already verified for email: ${email}`); + let status_code = 400; + return res.status(status_code).json({ + status_code: status_code, + message: 'Primary email is already verified.', + data: {} }); } catch(error){ - return res.status(400).json({ - status_code: 400, - message: error.toString() + logger.error(error.toString()); + let status_code = 500; + return res.status(status_code).json({ + status_code: status_code, + message: HttpStatus.getStatusText(status_code), + data: {} }); } } @@ -49,14 +80,21 @@ exports.checkInstiVerificationAccess = async(req, res, next) => { if(student.isVerified1 && !student.isVerified2){ return next(); } - return res.status(400).json({ - status_code: 400, - message: 'Cant access the required resource.' + + logger.info(`Already verified or Requested resource is blocked for email: ${email}`); + let status_code = 401; + return res.status(status_code).json({ + status_code: status_code, + message: `Can't access the required resource.`, + data: {} }); } catch(error){ - return res.status(400).json({ - status_code: 400, - message: error.toString() + logger.error(error.toString()); + let status_code = 500; + return res.status(status_code).json({ + status_code: status_code, + message: HttpStatus.getStatusText(status_code), + data: {} }); } } @@ -68,14 +106,21 @@ exports.checkAccountActivation = async(req, res, next) => { if(student.isVerified1 && student.isVerified2){ return next(); } - return res.status(400).json({ - status_code: 400, - message: 'Please complete institute email verification' + + logger.info(`Requested resource is blocked for email: ${email}`); + let status_code = 401; + return res.status(status_code).json({ + status_code: status_code, + message: 'Please complete institute email verification', + data: {} }); } catch(error){ - return res.status(400).json({ - status_code: 400, - message: error.toString() + logger.error(error.toString()); + let status_code = 500; + return res.status(status_code).json({ + status_code: status_code, + message: HttpStatus.getStatusText(status_code), + data: {} }); } } \ No newline at end of file diff --git a/src/middlewares/deadline.middleware.js b/src/middlewares/deadline.middleware.js index bf18155..165c6e9 100644 --- a/src/middlewares/deadline.middleware.js +++ b/src/middlewares/deadline.middleware.js @@ -1,48 +1,82 @@ +const HttpStatus = require('http-status-codes'); + // Importing config/env variables const config = require('../config/config'); +const logger = require('../config/winston'); + + +function dateDiffInDays(a, b) { + const _MS_PER_DAY = 1000 * 60 * 60 * 24; + const utc1 = Date.UTC(a.getFullYear(),a.getMonth(),a.getDate(),a.getHours(),a.getMinutes(),a.getSeconds()); + const utc2 = Date.UTC(b.getFullYear(),b.getMonth(),b.getDate(),b.getHours(),b.getMinutes(),b.getSeconds()); + return Math.floor((utc2 - utc1) / _MS_PER_DAY); +} + -exports.sendToMentorsDeadline = () => { +exports.sendToMentorsDeadline = async (req, res, next) => { try { - const deadline = new Date(`${config.date.END_DATE_JS}`); - const gap = deadline.getDate()-5; - const dateNow = new Date(); + let deadline = new Date(`${config.date.END_DATE_JS}`); + deadline.setHours(23); + deadline.setMinutes(59); + deadline.setSeconds(59); + + const dateTimeNow = new Date(); - if(dateNow < gap){ - next(); + const dateDiff = dateDiffInDays(dateTimeNow,deadline); + + if(dateDiff>=5){ + return next(); } - return res.status(400).json({ - status_code: 400, - message: `Sorry, can't send to mentors now.` + logger.info(`Action(send to mentors) is not allowed`); + let status_code = 401; + return res.status(status_code).json({ + status_code: status_code, + message: `Sorry, can't send to mentors now.`, + data: {} }); } catch(error){ - return res.status(400).json({ - status_code: 400, - message: error.toString() + logger.error(error.toString()); + let status_code = 500; + return res.status(status_code).json({ + status_code: status_code, + message: HttpStatus.getStatusText(status_code), + data: {} }); } } -exports.finalProjectDeadline = () => { +exports.finalProjectDeadline = async (req, res, next) => { try { - const deadline = new Date(`${config.date.END_DATE_JS}`); - const gap = deadline.getDate()-30; - const dateNow = new Date(); + let deadline = new Date(`${config.date.END_DATE_JS}`); + deadline.setHours(23); + deadline.setMinutes(59); + deadline.setSeconds(59); + + const dateTimeNow = new Date(); - if(dateNow > gap){ - next(); + const dateDiff = dateDiffInDays(dateTimeNow,deadline); + + if(dateDiff<=30 && dateDiff>=0){ + return next(); } - return res.status(400).json({ - status_code: 400, - message: 'Sorry, project can be uploaded only 30 days before deadline.' + logger.info(`Action(project abstract upload) is not allowed`); + let status_code = 401; + return res.status(status_code).json({ + status_code: status_code, + message: 'Sorry, project can be uploaded only 30 days before deadline.', + data: {} }); } catch(error){ - return res.status(400).json({ - status_code: 400, - message: error.toString() + logger.error(error.toString()); + let status_code = 500; + return res.status(status_code).json({ + status_code: status_code, + message: HttpStatus.getStatusText(status_code), + data: {} }); } } \ No newline at end of file diff --git a/src/models/student.model.js b/src/models/student.model.js index 4df9683..6e0df93 100644 --- a/src/models/student.model.js +++ b/src/models/student.model.js @@ -16,70 +16,169 @@ const studentSchema = new mongoose.Schema({ verificationCode: { type: String }, - instiVerificationCode: { - type: String - }, isVerified1: { - type: Boolean + type: Boolean, + default: false }, isVerified2: { - type: Boolean + type: Boolean, + default: false }, applicationNumber: { type: String, unique: true }, personalInfo: { - name: String, - rollNumber: String, - department: String, - contactNumberCall: String, - contactNumberWhatsapp: String, + name: { + type: String, + default: null + }, + rollNumber: { + type: String, + default: null + }, + department: { + type: String, + default: null + }, + contactNumberCall: { + type: String, + default: null + }, + contactNumberWhatsapp: { + type: String, + default: null + }, tshirtSize: { type: String, enum: ['XS','S','M','L','XL','XXL','XXXL'] }, - introduction: String + introduction: { + type: String, + default: null + }, + gender: { + type: String, + default: null + }, + disability: { + status: { + type: String, + enum: ['','Yes','No'], + default: '' + }, + description: { + type: String, + default: null + } + }, + nationality: { + type: String, + default: null + }, + firstGenStudent: { + type: String, + enum: ['','Yes','No'], + default: '' + }, + refugee: { + type: String, + enum: ['','Yes','Not applicable'], + default: '' + }, + pronoun: { + type: String, + enum: ['',`He/him/his`,`She/her/hers`,`They/them/their/theirs`], + default: '' + }, + filePath: { + type: String, + default: null + } }, certificates: { - gradeSheetSem1: String, - instiCertificate: String, - nonInstiCertificate: String, - gradeSheetMOOC: String + gradeSheetSem1: { + type: String, + default: null + }, + instiCertificate: { + type: String, + default: null + }, + nonInstiCertificate: { + type: String, + default: null + }, + gradeSheetMOOC: { + type: String, + default: null + } }, abstract: { - docLink: String, - projectAbstract: String, + docLink: { + type: String, + default: null + }, + projectAbstract: { + type: String, + default: null + }, supportingFiles: { type: Array } }, essays: { mentors: { - sop: String, - community: String, - society: String + sop: { + type: String, + default: null + }, + community: { + type: String, + default: null + }, + society: { + type: String, + default: null + } }, final: { - sop: String, - community: String, - society: String + sop: { + type: String, + default: null + }, + community: { + type: String, + default: null + }, + society: { + type: String, + default: null + } } }, signature: { - type: String + type: String, + default: null }, - isSubmitted: { - type: Boolean + applicationFilePath: { + type: String, + default: null }, - createdAt : { - type: Date, - default: Date.now + archive: { + summary: { + type: String, + default: null + }, + final: { + type: String, + default: null + } }, - updatedAt : { - type: Date, - default: Date.now + isSubmitted: { + type: Boolean, + default: false } -}); +},{timestamps: true}); module.exports = mongoose.model('Student', studentSchema); \ No newline at end of file diff --git a/src/routes/abstract.router.js b/src/routes/abstract.router.js index a08cbbc..ec97fa9 100644 --- a/src/routes/abstract.router.js +++ b/src/routes/abstract.router.js @@ -7,8 +7,9 @@ const abstractController = require('../controllers/abstract.controller'); const deadlineMiddleware = require('../middlewares/deadline.middleware'); // Student's abstracts routes -router.post('/mentors', deadlineMiddleware.sendToMentorsDeadline, abstractController.uploadDocLink); -router.post('/final/upload', deadlineMiddleware.finalProjectDeadline, abstractController.uploadFinalAbstract); -router.post('/final/view', abstractController.viewAbstract); +router.post('/mentors/send', deadlineMiddleware.sendToMentorsDeadline, abstractController.sendDocLink); +router.post('/final-abstract/upload', deadlineMiddleware.finalProjectDeadline, abstractController.uploadFinalAbstract); +router.post('/supporting-files/upload', deadlineMiddleware.finalProjectDeadline, abstractController.uploadSupportingFiles); +router.get('/final/view/all', abstractController.viewAbstract); module.exports = router; diff --git a/src/routes/auth/student.router.js b/src/routes/auth/student.router.js index 30451d7..35ba7c2 100644 --- a/src/routes/auth/student.router.js +++ b/src/routes/auth/student.router.js @@ -10,19 +10,20 @@ const checkInstiVerificationAccess = authMiddleware.checkInstiVerificationAccess const checkAccountInactivation = authMiddleware.checkAccountInactivation; // Primary email routes -router.post('/email/send-verification', checkAccountInactivation, studentController.sendVerificationCode); -router.post('/email/re-send-verification', checkAccountInactivation, studentController.reSendVerificationCode); -router.post('/email/verify', checkAccountInactivation, studentController.verifyEmail); +router.post('/email/verification-code/send', checkAccountInactivation, studentController.sendVerificationCode); +router.get('/email/verification-code/check', checkAccountInactivation, studentController.checkVerificationCode); // Institute(.edu) email routes -router.post('/insti-email/send-verification', checkStudentSession, checkInstiVerificationAccess, studentController.sendInstiVerificationCode); -router.post('/insti-email/re-send-verification', checkStudentSession, checkInstiVerificationAccess, studentController.reSendInstiVerificationCode); -router.post('/insti-email/verify', checkStudentSession, checkInstiVerificationAccess, studentController.verifyInstiEmail); +router.post('/insti-email/verification-code/send', checkStudentSession, checkInstiVerificationAccess, studentController.sendInstiVerificationCode); +router.post('/insti-email/verification-code/verify', checkStudentSession, checkInstiVerificationAccess, studentController.verifyInstiEmail); // Password routes router.post('/password/forgot', studentController.forgotPassword); router.post('/password/reset', studentController.resetPassword); +// Session routes +router.get('/session/check', studentController.checkSession); + // Auth routes router.post('/register', studentController.registerStudent); router.post('/login', studentController.loginStudent); diff --git a/src/routes/certificates.router.js b/src/routes/certificates.router.js index a2c37d0..cdfd6bd 100644 --- a/src/routes/certificates.router.js +++ b/src/routes/certificates.router.js @@ -4,7 +4,10 @@ const router = require('express').Router(); const certificatesController = require('../controllers/certificates.controller'); // Student's certificate routes -router.post('/upload', certificatesController.uploadCertificate); -router.post('/view', certificatesController.viewCertificates); +router.post('/gradeSheetSem1/upload', certificatesController.uploadGradeSheetSem1); +router.post('/instiCertificate/upload', certificatesController.uploadInstiCertificate); +router.post('/nonInstiCertificate/upload', certificatesController.uploadNonInstiCertificate); +router.post('/gradeSheetMOOC/upload', certificatesController.uploadGradeSheetMOOC); +router.get('/view/all', certificatesController.viewCertificates); module.exports = router; \ No newline at end of file diff --git a/src/routes/essays.router.js b/src/routes/essays.router.js index daeebe5..4ca97b9 100644 --- a/src/routes/essays.router.js +++ b/src/routes/essays.router.js @@ -5,14 +5,14 @@ const essayController = require('../controllers/essays.controller'); // Student's essays routes // Send to mentors routes -router.post('/mentors/sop/upload',essayController.uploadSOPToMentors); -router.post('/mentors/community/upload', essayController.uploadCommunityToMentors); -router.post('/mentors/society/upload', essayController.uploadSocietyToMentors); +router.post('/mentors/sop/send',essayController.sendSOPToMentors); +router.post('/mentors/community/send', essayController.sendCommunityToMentors); +router.post('/mentors/society/send', essayController.sendSocietyToMentors); // Final essays upload routes -router.post('/final/sop/upload', essayController.uploadSOPFinal); -router.post('/final/community/upload', essayController.uploadCommunityFinal); -router.post('/final/society/upload', essayController.uploadSocietyFinal); -router.post('/final/view/all', essayController.viewFinalEssays); +router.post('/final/sop/upload', essayController.uploadFinalSOP); +router.post('/final/community/upload', essayController.uploadFinalCommunity); +router.post('/final/society/upload', essayController.uploadFinalSociety); +router.get('/final/view/all', essayController.viewFinalEssays); module.exports = router; \ No newline at end of file diff --git a/src/routes/form.router.js b/src/routes/form.router.js index fa98c53..045b86d 100644 --- a/src/routes/form.router.js +++ b/src/routes/form.router.js @@ -3,8 +3,20 @@ const router = require('express').Router(); // Importing controllers const formController = require('../controllers/form.controller'); +// Importing middlewares +const authMiddleware = require('../middlewares/auth/student.middleware'); +const checkAccountActivation = authMiddleware.checkAccountActivation; + +// Form access and get routes +router.get('/access', formController.formAccess); +router.get('/application-number', formController.getApplicationNumber); + +// Form status routes +router.get('/status', checkAccountActivation, formController.getFormStatus); + // Final form submission routes -router.post('/preview', formController.previewForm); -router.post('/submit', formController.submitForm); +router.post('/generate', checkAccountActivation, formController.generateApplication); +router.get('/view', checkAccountActivation, formController.viewApplication); +router.post('/submit', checkAccountActivation, formController.submitForm); module.exports = router; \ No newline at end of file diff --git a/src/routes/index.router.js b/src/routes/index.router.js index 173cc32..58bc952 100644 --- a/src/routes/index.router.js +++ b/src/routes/index.router.js @@ -20,7 +20,7 @@ router.use('/abstract', checkStudentSession, checkAccountActivation, abstractRou router.use('/auth', authRouter); router.use('/certificates', checkStudentSession, checkAccountActivation, certificatesRouter); router.use('/essays', checkStudentSession, checkAccountActivation, essaysRouter); -router.use('/form', checkStudentSession, checkAccountActivation, formRouter); +router.use('/form', checkStudentSession, formRouter); router.use('/personal-info', checkStudentSession, checkAccountActivation, personalInfoRouter); router.use('/requirements', checkStudentSession, checkAccountActivation, requirementsRouter); router.use('/signature', checkStudentSession, checkAccountActivation, signatureRouter); diff --git a/src/routes/personalInfo.router.js b/src/routes/personalInfo.router.js index 6b4d4aa..5d26111 100644 --- a/src/routes/personalInfo.router.js +++ b/src/routes/personalInfo.router.js @@ -5,6 +5,6 @@ const personalInfoController = require('../controllers/personalInfo.controller') // Student's personal information routes router.post('/update', personalInfoController.updateInfo); -router.post('/details', personalInfoController.getInfo); +router.get('/view/all', personalInfoController.getInfo); module.exports = router; \ No newline at end of file diff --git a/src/routes/requirements.router.js b/src/routes/requirements.router.js index 648b651..2f1a733 100644 --- a/src/routes/requirements.router.js +++ b/src/routes/requirements.router.js @@ -4,7 +4,7 @@ const router = require('express').Router(); const requirementsController = require('../controllers/requirements.controller'); // Student's unfilled field routes -router.post('/pending/all', requirementsController.getAllPendingRequirements); -router.post('/pending/necessary', requirementsController.getNecessaryPendingRequirements); +router.get('/pending/all', requirementsController.getAllPendingRequirements); +router.get('/pending/necessary', requirementsController.getNecessaryPendingRequirements); module.exports = router; \ No newline at end of file diff --git a/src/routes/signature.router.js b/src/routes/signature.router.js index 41d5ad1..a3a6166 100644 --- a/src/routes/signature.router.js +++ b/src/routes/signature.router.js @@ -5,6 +5,6 @@ const signatureController = require('../controllers/signature.controller'); // Student's signature routes router.post('/update', signatureController.updateSignature); -router.post('/get', signatureController.getSignature); +router.get('/get', signatureController.getSignature); module.exports = router; \ No newline at end of file diff --git a/src/server.js b/src/server.js index bc1188b..042bb74 100644 --- a/src/server.js +++ b/src/server.js @@ -4,11 +4,13 @@ const cookieParser = require('cookie-parser'); const cors = require('cors'); const express = require('express'); const expressValidator = require('express-validator'); +const logger = require('./config/winston'); const mongoose = require('mongoose'); -const signale = require('signale'); -const sgMail = require('@sendgrid/mail'); +const morgan = require('morgan'); const session = require('express-session'); - +const MongoStore = require('connect-mongo')(session); +const sgMail = require('@sendgrid/mail'); +const signale = require('signale'); //Importing routes const routes = require('./routes/index.router'); @@ -17,13 +19,33 @@ const routes = require('./routes/index.router'); const config = require('./config/config'); // env/config variables -const PORT = config.PORT || 8000; -const MONGODB_URI = config.mongodb.DB_URI; +const APP_PORT = config.ports.APP_PORT || 8000; const SESSION_SECRET = config.session.secretString; +// Database connection options +let dbConnectionOptions = { + dbName: config.mongodb.DB_NAME, + reconnectTries: Number.MAX_SAFE_INTEGER, + useNewUrlParser: true, + useCreateIndex: true +}; + + +// Setting MONGODB_URI +let MONGODB_URI = `mongodb://${config.mongodb.DB_HOST}:${config.mongodb.DB_PORT}/${config.mongodb.DB_NAME}`; + +// Update db params if database authenication is enabled +if(config.mongodb.DB_AUTH_ENABLED){ + MONGODB_URI = `mongodb://${config.mongodb.DB_USERNAME}:${config.mongodb.DB_PASSWORD}@${config.mongodb.DB_HOST}:${config.mongodb.DB_PORT}/${config.mongodb.DB_NAME}?authSource=${config.mongodb.DB_AUTH_SOURCE}`; + + // Adding DB credentials + dbConnectionOptions.user = config.mongodb.DB_USERNAME; + dbConnectionOptions.pass = config.mongodb.DB_PASSWORD; +} + // Adding options for CORS middleware const corsOptions = { - origin: config.CORS_ORIGIN, + origin: config.url.CLIENT_BASE_URL, methods: ['GET', 'PUT', 'POST', 'DELETE'], credentials: true }; @@ -32,17 +54,25 @@ const corsOptions = { const app = express(); const router = express.Router(); + +// Configuring public path +app.use(express.static(config.directory.PUBLIC_DIR)); + +// Configure loggers +app.use(morgan('combined', { stream: logger.stream })); + // Database connection mongoose.Promise = global.Promise; -mongoose.connect(MONGODB_URI, { useNewUrlParser: true, useCreateIndex: true }) +mongoose.connect(MONGODB_URI, dbConnectionOptions) .then(() => { signale.success('*****Database Connection Successfull******'); - }).catch(err => { - signale.fatal(new Error(err)); + }).catch(error => { + logger.error(error.toString()); + signale.fatal(new Error(error)); signale.warn('Could not connect to Database. Exiting now...'); process.exit(); }); -let db = mongoose.connection; +let dbConnection = mongoose.connection; // Initialize body-parser middleware app.use(bodyParser.json(), cors(corsOptions)); @@ -74,17 +104,30 @@ app.use(expressValidator({ // Initialize express-session to allow us track the logged-in user across sessions. app.use(session({ secret: SESSION_SECRET, + store: new MongoStore({ + mongooseConnection: mongoose.connection + }), resave: true, - saveUninitialized: false + saveUninitialized: false, + cookie: { + maxAge: (1000 * 60 * 60) // 1 Hour + } })); -// Setting API key + +// Set Sendgrid API key sgMail.setApiKey(config.key.SENDGRID_API_KEY); app.use(routes); -app.get('/',(req,res) => { - res.send('What are you doing here? :p'); +app.get('/', (req, res) => { + return res.send('What are you doing here? :p'); +}); + +// Route error handler +app.use( (err, req, res, next) => { + // Log errors + logger.error(`${err.status || 500} - ${err.message} - ${req.originalUrl} - ${req.method} - ${req.ip}`); }); app.all('*', (req, res) => { @@ -93,13 +136,13 @@ app.all('*', (req, res) => { }); exports.start = () => { - app.listen(PORT, () => { - signale.success(`Listening on port: ${PORT}`) - }) + app.listen(APP_PORT, () => { + signale.success(`App server listening on port: ${APP_PORT}`); + }); } exports.stop = () => { - app.close(PORT, () => { - signale.success(`Shut down on port: ${PORT}`) - }) -} \ No newline at end of file + app.close(APP_PORT, () => { + signale.success(`App server shut down on port: ${APP_PORT}`); + }); +} diff --git a/src/templates/pdfs/personalInfo.ejs b/src/templates/pdfs/personalInfo.ejs new file mode 100644 index 0000000..aabf37d --- /dev/null +++ b/src/templates/pdfs/personalInfo.ejs @@ -0,0 +1,230 @@ + + + + Jitheshraj Scholarship Application Form + + + + +
+
Jitheshraj Scholarship <%= START_YEAR +"-"+ END_YEAR %>
+
Application Form
+
+
+
+
+ Application Number: + <%= student.applicationNumber %> +
+
+ Name: + <%= student.personalInfo.name %> +
+
+ Roll Number: + <%= student.personalInfo.rollNumber %> +
+
+ Department: + <%= student.personalInfo.department %> +
+
+ Preferred pronoun: + <%= student.personalInfo.pronoun %> +
+
+ T-Shirt Size: + <%= student.personalInfo.tshirtSize %> +
+
+ Signature (Full Name): + <%= student.signature %> +
+
+ Introduction: + <%= student.personalInfo.introduction %> +
+
+ E-Mail Address (Primary): + <%= student.email %> +
+
+ Institude E-Mail Address (Other): + <%= student.instiEmail %> +
+
+ Contact Number (Call): + <%= student.personalInfo.contactNumberCall %> +
+
+ Contact Number (Whatsapp): + <%= student.personalInfo.contactNumberWhatsapp %> +
+
+
+
Scanned copy of the original Semester 1 grade sheet
+
+ grade-sheet-sem1 +
+
+
+
Scanned copy of your Certificate of Participation or Merit of the +contest conducted by affiliates of NIT Trichy
+
+ Institute Merit Certificate +
+
+
+
Scanned copy of your Certificate of Participation or Merit of the contest conducted by institutes other than NIT Trichy
+
+ Non-Institute Merit Certificate +
+
+
+
Screenshot or Original grade sheet of the completed MOOC
+
+ grade-sheet-MOOC +
+
+
+ + + \ No newline at end of file diff --git a/src/utils/archive.util.js b/src/utils/archive.util.js new file mode 100644 index 0000000..ba24007 --- /dev/null +++ b/src/utils/archive.util.js @@ -0,0 +1,205 @@ +const archiver = require('archiver'); +const fs = require('fs'); +const HttpStatus = require('http-status-codes'); +const path = require('path'); + +// Importing config/env variables +const config = require('../config/config'); +const logger = require('../config/winston'); + + +exports.archiveSummary = async (student) => { + try { + let bool = true; + let archiveFileRelativePath = student.archive.summary; + let archiveFilePath = path.join(config.directory.PUBLIC_DIR,archiveFileRelativePath); + while(fs.existsSync(archiveFilePath)) + fs.unlink(archiveFilePath); + + let archive = archiver(config.files.archive.format); + let output = fs.createWriteStream(archiveFilePath); + + output.on('close', () => { + logger.info(archive.pointer() + ' total bytes'); + logger.info('Archiver has been finalized and the output file descriptor has closed.'); + }); + + output.on('end', () => { + logger.info('Data has been drained'); + }); + + archive.on('warning', (error) => { + if (error.code === 'ENOENT') { // stat failures + logger.error('ENOENT: File not found'); + throw('File not found'); + } else { + logger.error(error.toString()); + throw error; + } + }); + + archive.on('error', (error) => { + if (error.code === 'ENOENT') { // stat failures + logger.error('ENOENT: File not found'); + throw('File not found'); + } else { + logger.error(error.toString()); + throw error; + } + }); + + archive.pipe(output); + + // Add summary application pdf + let summaryApplication = path.join(config.directory.PUBLIC_DIR,student.applicationFilePath); + bool = bool && fs.existsSync(summaryApplication); + archive.file(summaryApplication,{name: path.basename(summaryApplication)}); + + // Add abstract's supporting files + for(let i=0;i { + try { + let bool = true; + let archiveFileRelativePath = student.archive.final; + let archiveFilePath = path.join(config.directory.PUBLIC_DIR,archiveFileRelativePath); + while(fs.existsSync(archiveFilePath)) + fs.unlink(archiveFilePath); + + let archive = archiver(config.files.archive.format); + let output = fs.createWriteStream(archiveFilePath); + + output.on('close', () => { + logger.info(archive.pointer() + ' total bytes'); + logger.info('Archiver has been finalized and the output file descriptor has closed.'); + }); + + output.on('end', function() { + logger.info('Data has been drained'); + }); + + archive.on('warning', (error) => { + if (error.code === 'ENOENT') { // stat failures + logger.error('ENOENT: File not found'); + throw('File not found'); + } else { + logger.error(error.toString()); + throw error; + } + }); + + archive.on('error', (error) => { + if (error.code === 'ENOENT') { // stat failures + logger.error('ENOENT: File not found'); + throw('File not found'); + } else { + logger.error(error.toString()); + throw error; + } + }); + + archive.pipe(output); + + // Add summary application + let summaryApplication = path.join(config.directory.PUBLIC_DIR,student.applicationFilePath); + bool = bool && fs.existsSync(summaryApplication); + archive.file(summaryApplication,{name: path.basename(summaryApplication)}); + + // Add certificates + Object.values(student.certificates).forEach( async (relativePath) => { + try { + let location = path.join(config.directory.PUBLIC_DIR,relativePath); + bool = bool && fs.existsSync(location); + archive.file(location,{name: path.basename(location)}); + } catch (error) { + logger.error(error.toString()); + let status_code = 500; + return { + status_code: status_code, + message: error.toString(), + data: {} + } + } + }); + + // Add abstract and supporting files + let abstract = path.join(config.directory.PUBLIC_DIR,student.abstract.projectAbstract); + bool = bool && fs.existsSync(abstract); + archive.file(abstract,{name: path.basename(student.abstract.projectAbstract)}); + for(let i=0;i { + try { + let location = path.join(config.directory.PUBLIC_DIR,relativePath); + bool = bool && fs.existsSync(location); + archive.file(location,{name: path.basename(relativePath)}); + } catch (error) { + logger.error(error.toString()); + let status_code = 500; + return { + status_code: status_code, + message: error.toString(), + data: {} + } + } + }); + + if(!bool){ + logger.error('File(s) not found.'); + throw new Error('File(s) not found.'); + } + + // Write everything to disk + await archive.finalize(); + + let status_code = 200; + return { + status_code: status_code, + message: HttpStatus.getStatusText(status_code), + data: {} + } + } catch (error) { + logger.error(error.toString()); + let status_code = 500; + return { + status_code: status_code, + message: error.toString(), + data: {} + } + } +} diff --git a/src/utils/form.util.js b/src/utils/form.util.js index af4f9cf..65d922c 100644 --- a/src/utils/form.util.js +++ b/src/utils/form.util.js @@ -1,19 +1,28 @@ +const fs = require('fs'); +const HttpStatus = require('http-status-codes'); +const path = require('path'); + +// Importing config/env variables +const config = require('../config/config'); +const logger = require('../config/winston'); + exports.checkFormSubmission = (student) => { let bool = ( student.email && student.instiEmail && student.verificationCode && - student.instiVerificationCode && student.isVerified1 && student.isVerified2 && student.applicationNumber && student.personalInfo && student.personalInfo.name && + student.personalInfo.rollNumber && student.personalInfo.department && student.personalInfo.contactNumberCall && student.personalInfo.contactNumberWhatsapp && student.personalInfo.tshirtSize && student.personalInfo.introduction && + student.personalInfo.pronoun && student.certificates && student.certificates.gradeSheetSem1 && student.certificates.instiCertificate && @@ -29,5 +38,57 @@ exports.checkFormSubmission = (student) => { student.essays.final.society && student.signature ); + + if(student.personalInfo.disability.status && student.personalInfo.disability.status=='Yes') + bool = bool && student.personalInfo.disability.description; + return bool; +} + +exports.returnFilesLocationAsArray = async (student) => { + try { + let bool = true; + let filesPathArray = new Array(); + + // Add personalInfo pdf + let personalInfoPath = path.join(config.directory.PUBLIC_DIR,student.personalInfo.filePath); + bool = bool && fs.existsSync(personalInfoPath); + filesPathArray.push(personalInfoPath); + + // Add project abstract + let abstractPath = path.join(config.directory.PUBLIC_DIR,student.abstract.projectAbstract); + bool = bool && fs.existsSync(abstractPath); + filesPathArray.push(abstractPath); + + // Add final version of essays (SOP, Community, Society) + let finalEssays = student.essays.final.toJSON(); + Object.values(finalEssays).forEach( (relativePath) => { + let location = path.join(config.directory.PUBLIC_DIR,relativePath); + bool = bool && fs.existsSync(location); + filesPathArray.push(location); + }); + + if(!bool){ + return { + status_code: 400, + message: 'File(s) not found. Try uploading again!', + data: {} + } + } + + return { + status_code: 200, + message: 'Success', + data: { + filesPathArray: filesPathArray + } + } + } catch (error) { + logger.error(error.toString()); + return { + status_code: 400, + message: error.toString(), + data: {} + } + } } \ No newline at end of file diff --git a/src/utils/googleDrive.util.js b/src/utils/googleDrive.util.js new file mode 100644 index 0000000..c598fad --- /dev/null +++ b/src/utils/googleDrive.util.js @@ -0,0 +1,245 @@ +const fs = require('fs'); +const { google } = require('googleapis'); +const HttpStatus = require('http-status-codes'); +const path = require('path'); +const readline = require('readline'); + +// Importing configuration/env variables +const config = require('../config/config'); +const logger = require('../config/winston'); + +// Global variables +const TOKEN_PATH = config.key.google_auth.TOKEN_PATH; +const SCOPES = ['https://www.googleapis.com/auth/drive']; + + +const authorize = async (credentials) => { + try{ + const { client_secret, client_id, redirect_uris } = credentials.installed; + let OAuth2Client = new google.auth.OAuth2( + client_id, client_secret, redirect_uris[0]); + + if(!fs.existsSync(TOKEN_PATH)) { + let accessResponse = await getAccessToken(OAuth2Client); + if(accessResponse.status_code!=200){ + throw (accessResponse.message); + } + logger.info(`Retrieving token from OAuth2Client.`); + OAuth2Client = accessResponse.data.OAuth2Client; + } + else{ + // Check if we have previously stored a token. + const tokenContent = await fs.readFileSync(TOKEN_PATH,'utf8'); + OAuth2Client.setCredentials(JSON.parse(tokenContent)); + logger.info(`Retrieved token from stored token.`); + + } + + let status_code = 200; + return { + status_code: status_code, + message: HttpStatus.getStatusText(status_code), + data: OAuth2Client + } + + } catch(error){ + logger.error(error.toString()); + let status_code = 500; + return { + status_code: status_code, + message: error.toString(), + data: {} + } + } +} + +const promptCode = async () => { + const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, + }); + return new Promise(function(resolve, reject) { + var ask = function() { + rl.question('Enter the code from that page here: ', function(code) { + rl.close(); + resolve(code); + }); + }; + ask(); + }); +} + +const getAccessToken = async (OAuth2Client) => { + try { + const authUrl = OAuth2Client.generateAuthUrl({ + access_type: 'offline', + scope: SCOPES, + }); + + logger.info(`Authorize this app by visiting this url: ${authUrl}`); + + if(config.environment=='production'){ + logger.info('Authorization required in production'); + let status_code = 401; + return { + status_code: status_code, + message: 'Authorization required in production', + data: {} + }; + } + else{ + console.log(`Authorize this app by visiting this url: `,authUrl); + } + + let code = await promptCode(); + const token = await OAuth2Client.getToken(code); + OAuth2Client.setCredentials(token); + + // Store the token to disk for later program executions + await fs.writeFileSync(TOKEN_PATH, JSON.stringify(token)); + logger.info(`OAuth2Client token stored to ${TOKEN_PATH}`); + + let status_code = 200; + return { + status_code: status_code, + message: HttpStatus.getStatusText(status_code), + data: { + OAuth2Client: OAuth2Client + } + }; + } catch(error) { + logger.error(error.toString()); + let status_code = 500; + return { + status_code: status_code, + message: error.toString(), + data: {} + }; + } + +} + +const useJWTAuthClient = async () => { + try{ + // configure a JWT auth client + let service_jwt = await fs.readFileSync(config.key.google_auth.service_jwt,'utf8'); + service_jwt = JSON.parse(service_jwt); + let jwtClient = new google.auth.JWT( + service_jwt.client_email, + null, + service_jwt.private_key, + ['https://www.googleapis.com/auth/drive'], + null + ); + + //authenticate request + await jwtClient.authorize(); + + logger.info(`Successfully authorized by jwtClient`); + let status_code = 200; + return { + status_code: status_code, + message: 'Successfully connected', + data: { + client: jwtClient + } + } + + } catch(error) { + logger.error(error.toString()); + let status_code = 500; + return { + status_code: status_code, + message: error.toString(), + data: {} + } + } +} + +const useOAuth2Client = async () => { + try { + let credentialsContent = await fs.readFileSync(config.key.google_auth.credentials,'utf8'); + let authResponse = await authorize(JSON.parse(credentialsContent)); + if(authResponse.status_code!=200 || !authResponse.data){ + throw (authResponse.message=='Success'? 'Failure in OAuth2Client': authResponse.message); + } + + logger.info(`Successfully retrieved token by OAuth2Client`); + let status_code = 200; + return { + status_code: status_code, + message: 'Successfully retrieved token', + data: { + client: authResponse.data.OAuth2Client + } + } + + } catch (error) { + logger.error(error.toString()); + let status_code = 500; + return { + status_code: status_code, + message: error.toString(), + data: {} + } + } +} + + +exports.uploadFinalApplication = async (finalArchivePath) => { + try { + const authClientResponse = await useOAuth2Client(); + if(authClientResponse.status_code!=200){ + throw (authClientResponse.message); + } + + let authClient = authClientResponse.data.client; + + let drive = await google.drive({ + version: 'v3', + auth: authClient + }); + + if(!fs.existsSync(finalArchivePath)){ + logger.error(`Final archive file doesn't exists`); + let status_code = 500; + return { + status_code: status_code, + message: `Final archive file doesn't exists`, + data: {} + } + } + + let driveResponse = await drive.files.create({ + resourse: { + name: path.basename(finalArchivePath), + mimeType: `${config.files.archive.mimeType}` + }, + requestBody: { + name: path.basename(finalArchivePath), + mimeType: `${config.files.archive.mimeType}` + }, + media: { + mimeType: `${config.files.archive.mimeType}`, + body: fs.createReadStream(finalArchivePath) + }, + fields: 'id' + }); + + logger.info(`File successfully uploaded to google drive.`); + return { + status_code: driveResponse.status, + message: driveResponse.statusText + } + + } catch (error) { + logger.error(error.toString()); + let status_code = 500; + return { + status_code: status_code, + message: error.toString(), + data: {} + } + } + +} \ No newline at end of file diff --git a/src/utils/mentors.util.js b/src/utils/mentors.util.js new file mode 100644 index 0000000..5794313 --- /dev/null +++ b/src/utils/mentors.util.js @@ -0,0 +1,55 @@ +const fs = require('fs'); +const HttpStatus = require('http-status-codes'); +const kue = require('kue'); +const logger = require('../config/winston'); + +// Importing configuration/env variables +const config = require('../config/config'); + +// Connections +const queue = kue.createQueue({ + prefix: 'q', + redis: { + port: config.redis.PORT, + host: config.redis.HOST + } +}); + +exports.createMailJobs = async (studentName, docLink, submissionType) => { + try { + const MENTORS_LIST_PATH = config.files.mentorsList; + const fileContent = await fs.readFileSync(MENTORS_LIST_PATH,'utf8'); + const mentorsList = JSON.parse(fileContent); + + // Create mailMentor jobs for each mentors + for(let i=0;i { + return (/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*#?&'()"+,-./:;<=>^_`|{}~])[A-Za-z\d@$!%*#?&'()"+,-./:;<=>^_`|{}~]{8,}$/).test(password); +} + exports.hashPassword = async(password) => { try { password = sanitize(password); diff --git a/src/utils/pdf.util.js b/src/utils/pdf.util.js new file mode 100644 index 0000000..465ecde --- /dev/null +++ b/src/utils/pdf.util.js @@ -0,0 +1,107 @@ +const delay = require('delay'); +const ejs = require('ejs'); +const fs = require('fs'); +const HttpStatus = require('http-status-codes'); +const pdfMerge = require('easy-pdf-merge'); +const wkhtmltopdf = require('wkhtmltopdf'); + +// Importing config/env variables +const config = require('../config/config'); +const logger = require('../config/winston'); + +const exportHTML = async (html,pdfOptions) => { + return new Promise((resolve, reject) => { + wkhtmltopdf(html, pdfOptions, (error) => { + if (error) { + logger.error(error); + reject(error); + } else { + resolve(); + } + }); + }); +} + +exports.generatePdf = async (student,ejsPath,destinationFilePath) => { + try { + let compileOptions = { + async: true + }; + let compiledEJS = await ejs.compile(fs.readFileSync(ejsPath, 'utf8'),compileOptions); + let html = await compiledEJS({ + student: student, + PUBLIC_DIR: config.directory.APP_STATIC_DIR, + START_YEAR: config.date.START_YEAR, + END_YEAR: config.date.END_YEAR + }); + + let pdfOptions = { + pageSize: 'A4', + output: destinationFilePath, + "margin-top": '20mm', + "margin-bottom": '20mm', + "margin-left": '20mm', + "margin-right": '20mm', + }; + + await exportHTML(html, pdfOptions); + + await delay(3000); + + let status_code = 200; + return { + status_code: status_code, + message: HttpStatus.getStatusText(status_code), + data: {} + } + + } catch (error) { + logger.error(error.toString()); + let status_code = 500; + return { + status_code: status_code, + message: error.toString(), + data: {} + } + } +} + +const promisePdfMerger = (sourceFilesArray,destinationFilePath) => { + return new Promise((resolve,reject) =>{ + pdfMerge(sourceFilesArray,destinationFilePath, (error) => { + if(error){ + logger.error(error.toString()); + reject(error); + } + + let status_code = 200; + let response = { + status_code: status_code, + message: 'PDFs have been successfully merged.', + data: {} + } + resolve(response); + }); + }); +} + +exports.mergePdf = async (sourceFilesArray,destinationFilePath) => { + try { + let mergerResponse = await promisePdfMerger(sourceFilesArray,destinationFilePath); + + await delay(4000); + return { + status_code: mergerResponse.status_code, + message: mergerResponse.message, + data: {} + } + } catch (error) { + logger.error(error.toString()); + let status_code = 500; + return { + status_code: status_code, + message: error.toString(), + data: {} + } + } +} \ No newline at end of file diff --git a/src/utils/register.util.js b/src/utils/register.util.js index 4772a97..e051862 100644 --- a/src/utils/register.util.js +++ b/src/utils/register.util.js @@ -16,4 +16,8 @@ exports.checkEmptyInput = (req) => { req.body.tshirtSize ); return bool; -} \ No newline at end of file +} + +exports.checkEduEmail = (email) => { + return (/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.+-]+\.edu$/).test(email); +} diff --git a/src/utils/requirements.util.js b/src/utils/requirements.util.js index 7812c17..858adde 100644 --- a/src/utils/requirements.util.js +++ b/src/utils/requirements.util.js @@ -1,7 +1,125 @@ exports.getAllPendingRequirements = (student) => { + let necessaryRequirements = this.getNecessaryPendingRequirements(student).necessaryRequirements; + + // Personal Information + let personalInformation = new Array(); + + if(!student.personalInfo.gender) + personalInformation.push('Gender'); + + if(!student.personalInfo.disability.status){ + personalInformation.push('Disability'); + } + else{ + if(student.personalInfo.disability.status=='Yes' && !student.personalInfo.disability.description) + personalInformation.push('Disability description'); + } + + if(!student.personalInfo.nationality) + personalInformation.push('Nationality'); + + if(!student.personalInfo.firstGenStudent) + personalInformation.push('First Generation Student status'); + + if(!student.personalInfo.refugee) + personalInformation.push('Refugee status'); + + + // Project Abstract + let abstract = new Array(); + + if(!student.abstract.supportingFiles) + abstract.push('Project abstract supporting files'); + + + // Essays + let essays = new Array(); + + if(!student.essays.mentors.sop) + abstract.push('Google document link of Statement of Purpose'); + + if(!student.essays.mentors.community) + abstract.push(`Google document link of 'For the community' essay`); + + if(!student.essays.mentors.society) + abstract.push(`Google document link of 'For the society' essay`); + + + let optionalRequirements = { + personalInfo: personalInformation, + certificates: [], + abstract: abstract, + essays: essays + } + + let allRequirements = { + necessaryRequirements: necessaryRequirements, + optionalRequirements: optionalRequirements + } + + return allRequirements; } exports.getNecessaryPendingRequirements = (student) => { + // Personal Information + let personalInformation = new Array(); + + if(!student.personalInfo.introduction) + personalInformation.push('Introduction'); + + if(!student.personalInfo.pronoun) + personalInformation.push('Preferred pronoun'); + + + // Certificates and Grade sheets + let certificates = new Array(); + + if(!student.certificates.gradeSheetSem1) + certificates.push('Scanned copy of the original Semester 1 grade sheet'); + + if(!student.certificates.instiCertificate) + certificates.push('Scanned copy of the Certificate of Participation or Merit of the contest conducted by affiliates of NIT Trichy'); + + if(!student.certificates.nonInstiCertificate) + certificates.push('Scanned copy of the Certificate of Participation or Merit of the contest conducted by institutes other than NIT Trichy'); + + if(!student.certificates.gradeSheetMOOC) + certificates.push('Screenshot or Original grade sheet of the completed MOOC'); + + + // Project Abstract + let abstract = new Array(); + + if(!student.abstract.docLink) + abstract.push('Google doc link of your project abstract'); + + if(!student.abstract.projectAbstract) + abstract.push('Abstract of your technical/interdisciplinary project'); + + + // Essays + let essays = new Array(); + + if(!student.essays.final.sop) + essays.push('Final version of Statement of Purpose'); + + if(!student.essays.final.community) + essays.push(`Final version of 'For the community' essay`); + + if(!student.essays.final.society) + essays.push(`Final version of 'For the society' essay`); + + + let necessaryRequirements = { + personalInfo: personalInformation, + certificates: certificates, + abstract: abstract, + essays: essays + } + + return { + necessaryRequirements: necessaryRequirements + } } \ No newline at end of file diff --git a/src/utils/sendgridMail.util.js b/src/utils/sendgridMail.util.js index 6cf002f..7f6e87f 100644 --- a/src/utils/sendgridMail.util.js +++ b/src/utils/sendgridMail.util.js @@ -1,8 +1,15 @@ +const fs = require('fs'); +const HttpStatus = require('http-status-codes'); +const logger = require('../config/winston'); +const path = require('path'); const sgMail = require('@sendgrid/mail'); // Importing configuration/env variables const config = require('../config/config'); +// Setting Sendgrid API key +sgMail.setApiKey(config.key.SENDGRID_API_KEY); + exports.sendVerificationCode = async (recipientEmail,verificationCode) => { try { const message = { @@ -11,7 +18,7 @@ exports.sendVerificationCode = async (recipientEmail,verificationCode) => { subject: 'Jitheshraj Scholarship Email Verification Code', html: `
-
Hi !
+
Hi,

Welcome to Jitheshraj Scholarship - ​An initiative of the academy for holistic education, awareness and development(AHEAD) Trust. In order to apply on the portal we require you to verify your account so enter the code given below into the verification box on the website.

@@ -25,14 +32,149 @@ exports.sendVerificationCode = async (recipientEmail,verificationCode) => { `, }; let mail = await sgMail.send(message); + let status_code = 200; + return { + status_code: status_code, + message: HttpStatus.getStatusText(status_code), + data: {} + } + } catch(error){ + logger.error(error.toString()); + let status_code = 500; + return { + status_code: status_code, + message: error.toString(), + data: {} + } + } +} + +exports.sendPasswordVerificationCode = async (recipientEmail,verificationCode) => { + try { + const message = { + from: config.email.SITE_NOREPLY_EMAIL, + to: recipientEmail, + subject: 'Jitheshraj Scholarship Password Reset Verification Code', + html: ` +
+
Hi,
+
+
You have requested for a password reset.
+
+
If this was you:
+
Great! There's nothing else you need to do.
+
+
Verification code: ${verificationCode}
+
+
Email info@jrscholarship.org if you have any questions.
+
+
Regards,
+
Team JSPF
+
+ `, + }; + let mail = await sgMail.send(message); + let status_code = 200; + return { + status_code: status_code, + message: HttpStatus.getStatusText(status_code), + data: {} + } + } catch(error){ + logger.error(error.toString()); + let status_code = 500; + return { + status_code: status_code, + message: error.toString(), + data: {} + } + } +} + +exports.sendEmailToMentors = async (mentorEmail,studentName,docLink,submissionType) => { + try { + const message = { + from: config.email.SITE_NOREPLY_EMAIL, + to: mentorEmail, + subject: 'Jitheshraj Scholarship Application', + html: ` +
+
Hello Sir/Ma'am,
+
+
Mr.${studentName} wants your suggestion on their ${submissionType}.
+
Kindly, check the URL ${docLink} and give your suggestion.
+
Thank you.
+
+
Email info@jrscholarship.org if you have any questions.
+
+
Regards,
+
Team JSPF
+
+ `, + }; + let mail = await sgMail.send(message); + let status_code = 200; + return { + status_code: status_code, + message: HttpStatus.getStatusText(status_code), + data: {} + } + } catch(error){ + logger.error(error.toString()); + let status_code = 500; + return { + status_code: status_code, + message: error.toString(), + data: {} + } + } +} + +exports.sendApplicationSummary = async (recipientEmail,applicationSummaryPath) => { + try { + let message = { + from: config.email.SITE_NOREPLY_EMAIL, + to: recipientEmail, + subject: 'Jitheshraj Scholarship Application Summary', + html: ` +
+
Hi,
+
+
Successfully received your application form for Jitheshraj Scholarship ${config.date.START_YEAR}-${config.date.END_YEAR}.
+
+
A summary of your application is attached with the mail for your future reference.
+
+
Email info@jrscholarship.org if you have any queries.
+
+
Regards,
+
Team JSPF
+
+ `, + attachments: [ + { + content: fs.readFileSync(applicationSummaryPath,{ encoding: "base64" }), + filename: 'JSPF-Application'+path.extname(applicationSummaryPath), + type: `${config.files.archive.mimeType}`, + disposition: 'attachment', + contentId: 'JSPF-Application' + }, + ], + }; + let mail = await sgMail.send(message); + + let status_code = 200; return { - status_code: 200, - message: `Success` + status_code: status_code, + message: HttpStatus.getStatusText(status_code), + data: {} } } catch(error){ + logger.error(error.toString()); + let status_code = 500; return { - status_code: 400, - message: error.toString() + status_code: status_code, + message: error.toString(), + data: {} } } -} \ No newline at end of file +} diff --git a/src/utils/upload.util.js b/src/utils/upload.util.js new file mode 100644 index 0000000..820c663 --- /dev/null +++ b/src/utils/upload.util.js @@ -0,0 +1,108 @@ +const HttpStatus = require('http-status-codes'); +const path = require('path'); +const multer = require('multer'); +const util = require('util'); + +// Importing config/env variables +const config = require('../config/config'); +const logger = require('../config/winston'); + + +class Uploader { + + constructor(req,applicationNumber,fileOptions) { + + // Set The Storage Engine + const storageOptions = multer.diskStorage({ + destination: path.join(config.directory.UPLOADS_DIR,applicationNumber), + filename: (req, file, cb) => { + cb(null, fileOptions.fileName + '_' + Date.now() + path.extname(file.originalname)); + }, + onError : (error, next) => { + logger.error(error.toString()); + let status_code = 500; + return { + status_code: status_code, + message: error.toString(), + data: {} + } + } + }); + + // Check File Type + const checkFileType = (file, allowedFileTypesRE, cb) => { + // Check extension + const extname = allowedFileTypesRE.test(path.extname(file.originalname).toLowerCase()); + // Check mimetype + const mimetype = allowedFileTypesRE.test(file.mimetype); + if (mimetype && extname) { + return cb(null, true); + } else { + logger.warn('Error: Upload only allowed file types'); + cb('Error: Upload only allowed file types'); + } + } + + this.upload = multer({ + storage: storageOptions, + limits: { fileSize: fileOptions.allowedFileSize }, + fileFilter: function (req, file, cb) { + checkFileType(file,fileOptions.allowedFileTypesRE,cb); + } + }); + + } + + async uploadSingle(req, res, fieldName) { + try { + const upload = util.promisify(this.upload.single(fieldName)); + let response = await upload(req, res); + + logger.info('File has been successfully uploaded.'); + let status_code = 200; + return { + status_code: status_code, + message: 'File has been successfully uploaded.', + data: { + file: req.file + } + } + } catch (error) { + logger.error(error.toString()); + let status_code = 500; + return { + status_code: status_code, + message: HttpStatus.getStatusText(status_code), + data: {} + } + } + } + + async uploadMulti(req, res, fieldDetails) { + try { + const upload = util.promisify(this.upload.array(fieldDetails.fieldName,fieldDetails.maxCount)); + let response = await upload(req, res); + + logger.info('Files have been successfully uploaded.'); + let status_code = 200; + return { + status_code: status_code, + message: 'Files have been successfully uploaded.', + data: { + files: req.files + } + } + } catch (error) { + logger.error(error.toString()); + let status_code = 500; + return { + status_code: status_code, + message: HttpStatus.getStatusText(status_code), + data: {} + } + } + } + +} + +module.exports = Uploader; diff --git a/src/workers/index.js b/src/workers/index.js new file mode 100644 index 0000000..bf421d6 --- /dev/null +++ b/src/workers/index.js @@ -0,0 +1,146 @@ +const kue = require('kue'); +const path = require('path'); + +// Importing config/env variables +const config = require('../config/config'); +const logger = require('../config/winston'); + +// Importing utils +const archiveUtil = require('../utils/archive.util'); +const googleDriveUtil = require('../utils/googleDrive.util'); +const sendgridMailUtil = require('../utils/sendgridMail.util'); + +// Connections +const queue = kue.createQueue({ + prefix: 'q', + redis: { + port: config.redis.PORT, + host: config.redis.HOST + } +}); + + +queue.on('error', (error) => { + logger.error(error.toString()); +}); + +queue.process('mailMentor', async (job, done)=>{ + try { + let mentorEmail = job.data.mentorEmail; + let studentName = job.data.studentName; + let docLink = job.data.docLink; + let submissionType = job.data.submissionType; + + let mailResponse = await sendgridMailUtil.sendEmailToMentors(mentorEmail,studentName,docLink,submissionType); + if(mailResponse.status_code!=200){ + logger.error(mailResponse.message); + done(new Error(mailResponse.message)); + } + + logger.info(`Successfully mailed to mentor ${mentorEmail} for student: ${studentName}`); + done(); + + } catch(error) { + logger.error(error.toString()); + done(new Error(error)); + } +}); + +queue.process('archiveSummary', async (job, done)=>{ + try { + let student = JSON.parse(job.data.student); + let archiveResponse = await archiveUtil.archiveSummary(student); + if(archiveResponse.status_code!=200){ + logger.error(archiveResponse.message); + done(new Error(archiveResponse.message)); + } + + // Mail summary application to student + let mailJob = await queue.create('mailSummary', { + email: student.email, + archiveFilePath: path.join(config.directory.PUBLIC_DIR,student.archive.summary) + }) + .removeOnComplete(true) + .attempts(5) + .backoff({type: 'exponential'}) + .save(); + mailJob.log(`${student.email}: Mail summary application job added to job queue.`); + logger.info(`${student.email}: Mail summary application job added to job queue.`); + + logger.info(`${student.email}: Summary application successfully archived.`); + done(); + + } catch(error) { + logger.error(error.toString()); + done(new Error(error)); + } +}); + +queue.process('archiveFinal', async (job, done)=>{ + try { + let student = JSON.parse(job.data.student); + let archiveResponse = await archiveUtil.archiveAllFinal(student); + if(archiveResponse.status_code!=200){ + logger.error(archiveResponse.message); + done(new Error(archiveResponse.message)); + } + + // Upload all final files to google drive + let uploadJob = await queue.create('driveUploadFinal', { + email: student.email, + finalArchivePath: path.join(config.directory.PUBLIC_DIR,student.archive.final) + }) + .removeOnComplete(true) + .attempts(5) + .backoff({type: 'exponential'}) + .save(); + uploadJob.log(`${student.email}: Upload final files to google drive job added to job queue.`); + logger.info(`${student.email}: Upload final files to google drive job added to job queue.`); + + logger.info(`${student.email}: Final application successfully archived.`); + done(); + } catch(error) { + logger.error(error.toString()); + done(new Error(error)); + } +}); + +queue.process('mailSummary', async (job, done)=>{ + try { + let email = job.data.email; + let archiveFilePath = job.data.archiveFilePath; + + let mailResponse = await sendgridMailUtil.sendApplicationSummary(email,archiveFilePath); + if(mailResponse.status_code!=200){ + logger.error(mailResponse.message); + done(new Error(mailResponse.message)); + } + + logger.info(`${email}: Summary application successfully mailed.`); + done(); + + } catch(error) { + logger.error(error.toString()); + done(new Error(error)); + } +}); + +queue.process('driveUploadFinal', async (job, done)=>{ + try { + let email = job.data.email; + let finalArchivePath = job.data.finalArchivePath; + + let uploadResponse = await googleDriveUtil.uploadFinalApplication(finalArchivePath); + if(uploadResponse.status_code!=200){ + logger.error(uploadResponse.message); + done(new Error(uploadResponse.message)); + } + + logger.info(`${email}: Final application successfully uploaded to google drive.`); + done(); + + } catch(error) { + logger.error(error.toString()); + done(new Error(error)); + } +}); \ No newline at end of file diff --git a/storage/logs/.gitignore b/storage/logs/.gitignore new file mode 100644 index 0000000..c96a04f --- /dev/null +++ b/storage/logs/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file