diff --git a/.babelrc b/.babelrc new file mode 100644 index 00000000..b3481d9f --- /dev/null +++ b/.babelrc @@ -0,0 +1,9 @@ +{ + "presets": [ + ["@babel/preset-env", { + "targets": { + "node": "current" + } + }] + ] +} diff --git a/.codeclimate.yml b/.codeclimate.yml new file mode 100644 index 00000000..8ed51f01 --- /dev/null +++ b/.codeclimate.yml @@ -0,0 +1,20 @@ +version: '2' +plugins: + rubocop: + enabled: true + eslint: + enabled: true +exclude_patterns: +- "src/config/" +- "src/models/" +- "src/migrations/" +- "src/seeders/" +- "src/helpers/factory" +- "**/node_modules/" +- "templates/" +- "script/" +- "**/spec/" +- "**/test/" +- "**/tests/" +- "**/vendor/" +- "**/*.d.ts" diff --git a/.coveralls.yml b/.coveralls.yml new file mode 100644 index 00000000..5cd38958 --- /dev/null +++ b/.coveralls.yml @@ -0,0 +1,2 @@ +service_name: travis-ci +repo_token: 4RaaumwJ4Nj5xob1VFKxIFozVkinsSyVw \ No newline at end of file diff --git a/.env.sample b/.env.sample new file mode 100644 index 00000000..59f6ebed --- /dev/null +++ b/.env.sample @@ -0,0 +1,38 @@ +APP_URL_BACKEND = http://localhost:3000 +APP_URL_FRONTEND = http://localhost:3000/api/v1 +SECRET_KEY = 12345 + +DATABASE_URL_DEV = postgres://postgres:12345@localhost:5432/authorhavens_dev +DATABASE_URL_TEST = postgres://postgres:12345@localhost:5432/authorhavens_test + +DB_NAME_DEV=authorhavens_dev +DB_HOST_DEV=localhost +DB_USER_DEV=postgres +DB_PASSWORD_DEV=123456 +DB_PORT_DEV=5432 + +DB_NAME_TEST=authorhavens_test +DB_HOST_TEST=localhost +DB_USER_TEST=postgres +DB_PASSWORD_TEST=123456 +DB_PORT_TEST=5432 + +FACEBOOK_APP_ID = FACEBOOK_APP_ID +FACEBOOK_APP_SECRET = FACEBOOK_APP_SECRET + +TWITTER_CONSUMER_KEY = TWITTER_CONSUMER_KEY +TWITTER_CONSUMER_SECRET = TWITTER_CONSUMER_SECRET + +GOOGLE_CONSUMER_KEY = GOOGLE_CONSUMER_KEY +GOOGLE_CONSUMER_SECRET = GOOGLE_CONSUMER_SECRET + +EMAIL_SENDER = EMAIL_SENDER +SENDGRID_API_KEY = SENDGRID_API_KEY + +AWS_ACCESS_KEY_ID = AWS_ACCESS_KEY_ID +AWS_SECRET_ACCESS_KEY = AWS_SECRET_ACCESS_KEY + +CLOUDINARY_CLOUD_NAME = CLOUDINARY_CLOUD_NAME +CLOUDINARY_API_KEY = CLOUDINARY_API_KEY +CLOUDINARY_API_SECRET = CLOUDINARY_API_SECRET +IMAGE_BASE_URL = IMAGE_BASE_URL \ No newline at end of file diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 00000000..bac321c7 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,56 @@ +{ + "root": true, + "extends": "airbnb-base", + "env": { + "node": true, + "es6": true, + "mocha": true + }, + "rules": { + "one-var": 0, + "one-var-declaration-per-line": 0, + "new-cap": 0, + "consistent-return": 0, + "no-param-reassign": 0, + "comma-dangle": 0, + "curly": [ + "error", + "multi-line" + ], + "import/no-unresolved": [ + 2, + { + "commonjs": true + } + ], + "no-shadow": [ + "error", + { + "allow": [ + "req", + "res", + "err" + ] + } + ], + "valid-jsdoc": [ + "error", + { + "requireReturn": true, + "requireReturnType": true, + "requireParamDescription": false, + "requireReturnDescription": true + } + ], + "require-jsdoc": [ + "error", + { + "require": { + "FunctionDeclaration": true, + "MethodDefinition": true, + "ClassDeclaration": true + } + } + ] + } +} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 731fdd1b..216d3d0e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,14 +1,15 @@ # Logs logs *.log -.DS_Store - npm-debug.log* +yarn-debug.log* +yarn-error.log* # Runtime data pids *.pid *.seed +*.pid.lock # Directory for instrumented libs generated by jscoverage/JSCover lib-cov @@ -16,21 +17,46 @@ lib-cov # Coverage directory used by tools like istanbul coverage +# nyc test coverage +.nyc_output + # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) .grunt +# Bower dependency directory (https://bower.io/) +bower_components + # node-waf configuration .lock-wscript -# Compiled binary addons (http://nodejs.org/api/addons.html) +# Compiled binary addons (https://nodejs.org/api/addons.html) build/Release -# Dependency directory -node_modules +# Dependency directories +node_modules/ +jspm_packages/ + +# TypeScript v1 declaration files +typings/ # Optional npm cache directory .npm +# Optional eslint cache +.eslintcache + # Optional REPL history .node_repl_history +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env + +# next.js build output +.next +.DS_Store diff --git a/.nycrc b/.nycrc new file mode 100644 index 00000000..18e5f2fc --- /dev/null +++ b/.nycrc @@ -0,0 +1,10 @@ +{ + "reporter": [ + "lcov", + "text" + ], + "exclude": [ + "src/tests", + "swagger.json" + ] +} \ No newline at end of file diff --git a/.sequelizerc b/.sequelizerc new file mode 100644 index 00000000..b529a07c --- /dev/null +++ b/.sequelizerc @@ -0,0 +1,10 @@ +require("@babel/register"); + +const path = require('path'); + +module.exports = { + "config": path.resolve('./src/config', 'dbConfig.js'), + "models-path": path.resolve('./src/models'), + "migrations-path": path.resolve('./src/migrations'), + "seeders-path": path.resolve('./src/seeders') +}; \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..27bf3548 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,35 @@ +env: + global: + - CI=true + - SECRET_KEY=12345 + - NODE_ENV=test + - CC_TEST_REPORTER_ID=d570e2625aaffa20e7828eaddfbec748c525e866b6d413472bc26e188cb2bc6a + - DATABASE_URL_TEST=postgres://postgres@localhost:5432/authorhavens_test + - APP_URL_BACKEND=http://localhost:3000 + - APP_URL_FRONTEND=http://localhost:5000 + - FACEBOOK_APP_ID=FacebookId + - FACEBOOK_APP_SECRET=FacebookSecret + - TWITTER_CONSUMER_KEY=TwitterKey + - TWITTER_CONSUMER_SECRET=TwitterKey + - GOOGLE_CONSUMER_KEY=ToogleKey + - GOOGLE_CONSUMER_SECRET=GoogleSecret +language: node_js +node_js: + - 'stable' +cache: + directories: + - 'node_modules' +services: + - postgresql +before_script: + - psql -c 'CREATE DATABASE authorhavens_test;' -U postgres + - npm i sequelize-cli -g + - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter + - chmod +x ./cc-test-reporter + - ./cc-test-reporter before-build +script: + - npm run cover +after_script: + - ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT +after_success: + - npm run coveralls diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..a5a29f28 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Andela + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 4d9fbbfe..1b6cfd27 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,16 @@ -Authors Haven - A Social platform for the creative at heart. -======= +[![Build Status](https://travis-ci.org/andela/ninjas-ah-backend.svg?branch=develop)](https://travis-ci.org/andela/ninjas-ah-backend) [![Coverage Status](https://coveralls.io/repos/github/andela/ninjas-ah-backend/badge.svg?branch=develop)](https://coveralls.io/github/andela/ninjas-ah-backend?branch=develop) [![Maintainability](https://api.codeclimate.com/v1/badges/4635ecd43e1b06546534/maintainability)](https://codeclimate.com/github/andela/ninjas-ah-backend/maintainability) [![Test Coverage](https://api.codeclimate.com/v1/badges/4635ecd43e1b06546534/test_coverage)](https://codeclimate.com/github/andela/ninjas-ah-backend/test_coverage) + +# Authors Haven - A Social platform for the creative at heart. ## Vision + Create a community of like minded authors to foster inspiration and innovation by leveraging the modern web. --- ## API Spec + The preferred JSON object to be returned by the API should be structured as follows: ### Users (for authentication) @@ -23,7 +26,9 @@ The preferred JSON object to be returned by the API should be structured as foll } } ``` + ### Profile + ```source-json { "profile": { @@ -34,7 +39,9 @@ The preferred JSON object to be returned by the API should be structured as foll } } ``` + ### Single Article + ```source-json { "article": { @@ -56,7 +63,9 @@ The preferred JSON object to be returned by the API should be structured as foll } } ``` + ### Multiple Articles + ```source-json { "articles":[{ @@ -96,7 +105,9 @@ The preferred JSON object to be returned by the API should be structured as foll "articlesCount": 2 } ``` + ### Single Comment + ```source-json { "comment": { @@ -113,7 +124,9 @@ The preferred JSON object to be returned by the API should be structured as foll } } ``` + ### Multiple Comments + ```source-json { "comments": [{ @@ -131,7 +144,9 @@ The preferred JSON object to be returned by the API should be structured as foll "commentsCount": 1 } ``` + ### List of Tags + ```source-json { "tags": [ @@ -140,7 +155,9 @@ The preferred JSON object to be returned by the API should be structured as foll ] } ``` + ### Errors and Status Codes + If a request fails any validations, expect errors in the following format: ```source-json @@ -152,16 +169,16 @@ If a request fails any validations, expect errors in the following format: } } ``` + ### Other status codes: + 401 for Unauthorized requests, when a request requires authentication but it isn't provided 403 for Forbidden requests, when a request may be valid but the user doesn't have permissions to perform the action 404 for Not found requests, when a resource can't be found to fulfill the request - -Endpoints: ----------- +## Endpoints: ### Authentication: @@ -178,9 +195,9 @@ Example request body: } ``` -No authentication required, returns a User +No authentication required, returns a User -Required fields: `email`, `password` +Required fields: `email`, `password` ### Registration: @@ -198,15 +215,15 @@ Example request body: } ``` -No authentication required, returns a User +No authentication required, returns a User -Required fields: `email`, `username`, `password` +Required fields: `email`, `username`, `password` ### Get Current User `GET /api/user` -Authentication required, returns a User that's the current user +Authentication required, returns a User that's the current user ### Update User @@ -224,21 +241,21 @@ Example request body: } ``` -Authentication required, returns the User +Authentication required, returns the User -Accepted fields: `email`, `username`, `password`, `image`, `bio` +Accepted fields: `email`, `username`, `password`, `image`, `bio` ### Get Profile `GET /api/profiles/:username` -Authentication optional, returns a Profile +Authentication optional, returns a Profile ### Follow user `POST /api/profiles/:username/follow` -Authentication required, returns a Profile +Authentication required, returns a Profile No additional parameters required @@ -246,7 +263,7 @@ No additional parameters required `DELETE /api/profiles/:username/follow` -Authentication required, returns a Profile +Authentication required, returns a Profile No additional parameters required @@ -254,7 +271,7 @@ No additional parameters required `GET /api/articles` -Returns most recent articles globally by default, provide `tag`, `author` or `favorited` query parameter to filter results +Returns most recent articles globally by default, provide `tag`, `author` or `favorited` query parameter to filter results Query Parameters: @@ -278,21 +295,21 @@ Offset/skip number of articles (default is 0): `?offset=0` -Authentication optional, will return multiple articles, ordered by most recent first +Authentication optional, will return multiple articles, ordered by most recent first ### Feed Articles `GET /api/articles/feed` -Can also take `limit` and `offset` query parameters like List Articles +Can also take `limit` and `offset` query parameters like List Articles -Authentication required, will return multiple articles created by followed users, ordered by most recent first. +Authentication required, will return multiple articles created by followed users, ordered by most recent first. ### Get Article `GET /api/articles/:slug` -No authentication required, will return single article +No authentication required, will return single article ### Create Article @@ -311,11 +328,11 @@ Example request body: } ``` -Authentication required, will return an Article +Authentication required, will return an Article -Required fields: `title`, `description`, `body` +Required fields: `title`, `description`, `body` -Optional fields: `tagList` as an array of Strings +Optional fields: `tagList` as an array of Strings ### Update Article @@ -331,11 +348,11 @@ Example request body: } ``` -Authentication required, returns the updated Article +Authentication required, returns the updated Article -Optional fields: `title`, `description`, `body` +Optional fields: `title`, `description`, `body` -The `slug` also gets updated when the `title` is changed +The `slug` also gets updated when the `title` is changed ### Delete Article @@ -357,14 +374,14 @@ Example request body: } ``` -Authentication required, returns the created Comment -Required field: `body` +Authentication required, returns the created Comment +Required field: `body` ### Get Comments from an Article `GET /api/articles/:slug/comments` -Authentication optional, returns multiple comments +Authentication optional, returns multiple comments ### Delete Comment @@ -376,14 +393,14 @@ Authentication required `POST /api/articles/:slug/favorite` -Authentication required, returns the Article +Authentication required, returns the Article No additional parameters required ### Unfavorite Article `DELETE /api/articles/:slug/favorite` -Authentication required, returns the Article +Authentication required, returns the Article No additional parameters required diff --git a/config/index.js b/config/index.js deleted file mode 100644 index cdf8f90a..00000000 --- a/config/index.js +++ /dev/null @@ -1,4 +0,0 @@ -module.exports = { - secret: - process.env.NODE_ENV === "production" ? process.env.SECRET : "secret" -}; diff --git a/config/passport.js b/config/passport.js deleted file mode 100644 index 3b1d7699..00000000 --- a/config/passport.js +++ /dev/null @@ -1,26 +0,0 @@ -const passport = require("passport"); -const LocalStrategy = require("passport-local").Strategy; -const mongoose = require("mongoose"); -const User = mongoose.model("User"); - -passport.use( - new LocalStrategy( - { - usernameField: "user[email]", - passwordField: "user[password]" - }, - function(email, password, done) { - User.findOne({ email: email }) - .then(function(user) { - if (!user || !user.validPassword(password)) { - return done(null, false, { - errors: { "email or password": "is invalid" } - }); - } - - return done(null, user); - }) - .catch(done); - } - ) -); diff --git a/index.js b/index.js deleted file mode 100644 index a10a6414..00000000 --- a/index.js +++ /dev/null @@ -1,93 +0,0 @@ -const fs = require("fs"), - http = require("http"), - path = require("path"), - methods = require("methods"), - express = require("express"), - bodyParser = require("body-parser"), - session = require("express-session"), - cors = require("cors"), - passport = require("passport"), - errorhandler = require("errorhandler"), - mongoose = require("mongoose"); - -const isProduction = process.env.NODE_ENV === "production"; - -// Create global app object -const app = express(); - -app.use(cors()); - -// Normal express config defaults -app.use(require("morgan")("dev")); -app.use(bodyParser.urlencoded({ extended: false })); -app.use(bodyParser.json()); - -app.use(require("method-override")()); -app.use(express.static(__dirname + "/public")); - -app.use( - session({ - secret: "authorshaven", - cookie: { maxAge: 60000 }, - resave: false, - saveUninitialized: false - }) -); - -if (!isProduction) { - app.use(errorhandler()); -} - -if (isProduction) { - mongoose.connect(process.env.MONGODB_URI); -} else { - mongoose.connect("mongodb://localhost/conduit"); - mongoose.set("debug", true); -} - -require("./models/User"); - -app.use(require("./routes")); - -/// catch 404 and forward to error handler -app.use(function(req, res, next) { - const err = new Error("Not Found"); - err.status = 404; - next(err); -}); - -/// error handlers - -// development error handler -// will print stacktrace -if (!isProduction) { - app.use(function(err, req, res, next) { - console.log(err.stack); - - res.status(err.status || 500); - - res.json({ - errors: { - message: err.message, - error: err - } - }); - }); -} - -// production error handler -// no stacktraces leaked to user -app.use(function(err, req, res, next) { - res.status(err.status || 500); - res.json({ - errors: { - message: err.message, - error: {} - } - }); -}); - -// finally, let's start our server... -const server = app.listen(process.env.PORT || 3000, function() { - console.log("Listening on port " + server.address().port); -}); diff --git a/models/User.js b/models/User.js deleted file mode 100644 index 10d0379f..00000000 --- a/models/User.js +++ /dev/null @@ -1,57 +0,0 @@ -const mongoose = require("mongoose"); -const uniqueValidator = require("mongoose-unique-validator"); -const crypto = require("crypto"); -const secret = require("../config").secret; - -const UserSchema = new mongoose.Schema( - { - username: { - type: String, - lowercase: true, - unique: true, - required: [true, "can't be blank"], - match: [/^[a-zA-Z0-9]+$/, "is invalid"], - index: true - }, - email: { - type: String, - lowercase: true, - unique: true, - required: [true, "can't be blank"], - match: [/\S+@\S+\.\S+/, "is invalid"], - index: true - }, - bio: String, - image: String, - favorites: [{ type: mongoose.Schema.Types.ObjectId, ref: "Article" }], - following: [{ type: mongoose.Schema.Types.ObjectId, ref: "User" }], - hash: String, - salt: String - }, - { timestamps: true } -); - -UserSchema.plugin(uniqueValidator, { message: "is already taken." }); - -UserSchema.methods.validPassword = function(password) { - const hash = crypto - .pbkdf2Sync(password, this.salt, 10000, 512, "sha512") - .toString("hex"); - return this.hash === hash; -}; - -UserSchema.methods.setPassword = function(password) { - this.salt = crypto.randomBytes(16).toString("hex"); - this.hash = crypto - .pbkdf2Sync(password, this.salt, 10000, 512, "sha512") - .toString("hex"); -}; - -UserSchema.methods.toAuthJSON = function() { - return { - username: this.username, - email: this.email - }; -}; - -mongoose.model("User", UserSchema); diff --git a/nodemon.json b/nodemon.json new file mode 100644 index 00000000..dc5f1a2f --- /dev/null +++ b/nodemon.json @@ -0,0 +1,4 @@ +{ + "watch": ["src/"], + "ignore": ["src/tests/"] +} diff --git a/package-lock.json b/package-lock.json index bd198829..2941a66e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,570 +4,782 @@ "lockfileVersion": 1, "requires": true, "dependencies": { - "accepts": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", - "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", + "@babel/cli": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.4.4.tgz", + "integrity": "sha512-XGr5YjQSjgTa6OzQZY57FAJsdeVSAKR/u/KA5exWIz66IKtv/zXtHy+fIZcMry/EgYegwuHE7vzGnrFhjdIAsQ==", "requires": { - "mime-types": "~2.1.18", - "negotiator": "0.6.1" + "chokidar": "^2.0.4", + "commander": "^2.8.1", + "convert-source-map": "^1.1.0", + "fs-readdir-recursive": "^1.1.0", + "glob": "^7.0.0", + "lodash": "^4.17.11", + "mkdirp": "^0.5.1", + "output-file-sync": "^2.0.0", + "slash": "^2.0.0", + "source-map": "^0.5.0" } }, - "ajv": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", - "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "@babel/code-frame": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", + "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", "requires": { - "co": "^4.6.0", - "fast-deep-equal": "^1.0.0", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.3.0" + "@babel/highlight": "^7.0.0" } }, - "array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" - }, - "asn1": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", - "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=" + "@babel/core": { + "version": "7.4.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.4.5.tgz", + "integrity": "sha512-OvjIh6aqXtlsA8ujtGKfC7LYWksYSX8yQcM8Ay3LuvVeQ63lcOKgoZWVqcpFwkd29aYU9rVx7jxhfhiEDV9MZA==", + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/generator": "^7.4.4", + "@babel/helpers": "^7.4.4", + "@babel/parser": "^7.4.5", + "@babel/template": "^7.4.4", + "@babel/traverse": "^7.4.5", + "@babel/types": "^7.4.4", + "convert-source-map": "^1.1.0", + "debug": "^4.1.0", + "json5": "^2.1.0", + "lodash": "^4.17.11", + "resolve": "^1.3.2", + "semver": "^5.4.1", + "source-map": "^0.5.0" + }, + "dependencies": { + "@babel/parser": { + "version": "7.4.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.4.5.tgz", + "integrity": "sha512-9mUqkL1FF5T7f0WDFfAoDdiMVPWsdD1gZYzSnaXsxUCUqzuch/8of9G3VUSNiZmMBoRxT3neyVsqeiL/ZPcjew==" + }, + "@babel/traverse": { + "version": "7.4.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.4.5.tgz", + "integrity": "sha512-Vc+qjynwkjRmIFGxy0KYoPj4FdVDxLej89kMHFsWScq999uX+pwcX4v9mWRjW0KcAYTPAuVQl2LKP1wEVLsp+A==", + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/generator": "^7.4.4", + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-split-export-declaration": "^7.4.4", + "@babel/parser": "^7.4.5", + "@babel/types": "^7.4.4", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.11" + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + } + } }, - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + "@babel/generator": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.4.4.tgz", + "integrity": "sha512-53UOLK6TVNqKxf7RUh8NE851EHRxOOeVXKbK2bivdb+iziMyk03Sr4eaE9OELCbyZAAafAKPDwF2TPUES5QbxQ==", + "requires": { + "@babel/types": "^7.4.4", + "jsesc": "^2.5.1", + "lodash": "^4.17.11", + "source-map": "^0.5.0", + "trim-right": "^1.0.1" + } }, - "async": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=" + "@babel/helper-annotate-as-pure": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.0.0.tgz", + "integrity": "sha512-3UYcJUj9kvSLbLbUIfQTqzcy5VX7GRZ/CCDrnOaZorFFM01aXp1+GJwuFGV4NDDoAS+mOUyHcO6UD/RfqOks3Q==", + "requires": { + "@babel/types": "^7.0.0" + } }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + "@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.1.0.tgz", + "integrity": "sha512-qNSR4jrmJ8M1VMM9tibvyRAHXQs2PmaksQF7c1CGJNipfe3D8p+wgNwgso/P2A2r2mdgBWAXljNWR0QRZAMW8w==", + "requires": { + "@babel/helper-explode-assignable-expression": "^7.1.0", + "@babel/types": "^7.0.0" + } }, - "aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" + "@babel/helper-call-delegate": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/helper-call-delegate/-/helper-call-delegate-7.4.4.tgz", + "integrity": "sha512-l79boDFJ8S1c5hvQvG+rc+wHw6IuH7YldmRKsYtpbawsxURu/paVy57FZMomGK22/JckepaikOkY0MoAmdyOlQ==", + "requires": { + "@babel/helper-hoist-variables": "^7.4.4", + "@babel/traverse": "^7.4.4", + "@babel/types": "^7.4.4" + } }, - "aws4": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.7.0.tgz", - "integrity": "sha512-32NDda82rhwD9/JBCCkB+MRYDp0oSvlo2IL6rQWA10PQi7tDUM3eqMSltXmY+Oyl/7N3P3qNtAlv7X0d9bI28w==" + "@babel/helper-define-map": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.4.4.tgz", + "integrity": "sha512-IX3Ln8gLhZpSuqHJSnTNBWGDE9kdkTEWl21A/K7PQ00tseBwbqCHTvNLHSBd9M0R5rER4h5Rsvj9vw0R5SieBg==", + "requires": { + "@babel/helper-function-name": "^7.1.0", + "@babel/types": "^7.4.4", + "lodash": "^4.17.11" + } }, - "basic-auth": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.0.tgz", - "integrity": "sha1-AV2z81PgLlY3d1X5YnQuiYHnu7o=", + "@babel/helper-explode-assignable-expression": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.1.0.tgz", + "integrity": "sha512-NRQpfHrJ1msCHtKjbzs9YcMmJZOg6mQMmGRB+hbamEdG5PNpaSm95275VD92DvJKuyl0s2sFiDmMZ+EnnvufqA==", "requires": { - "safe-buffer": "5.1.1" + "@babel/traverse": "^7.1.0", + "@babel/types": "^7.0.0" } }, - "bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", - "optional": true, + "@babel/helper-function-name": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz", + "integrity": "sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw==", "requires": { - "tweetnacl": "^0.14.3" + "@babel/helper-get-function-arity": "^7.0.0", + "@babel/template": "^7.1.0", + "@babel/types": "^7.0.0" } }, - "bluebird": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.0.tgz", - "integrity": "sha1-eRQg1/VR7qKJdFOop3ZT+WYG1nw=" + "@babel/helper-get-function-arity": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz", + "integrity": "sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ==", + "requires": { + "@babel/types": "^7.0.0" + } }, - "body-parser": { - "version": "1.18.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz", - "integrity": "sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ=", + "@babel/helper-hoist-variables": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.4.4.tgz", + "integrity": "sha512-VYk2/H/BnYbZDDg39hr3t2kKyifAm1W6zHRfhx8jGjIHpQEBv9dry7oQ2f3+J703TLu69nYdxsovl0XYfcnK4w==", "requires": { - "bytes": "3.0.0", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "~1.1.2", - "http-errors": "~1.6.3", - "iconv-lite": "0.4.23", - "on-finished": "~2.3.0", - "qs": "6.5.2", - "raw-body": "2.3.3", - "type-is": "~1.6.16" + "@babel/types": "^7.4.4" } }, - "bson": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/bson/-/bson-1.0.9.tgz", - "integrity": "sha512-IQX9/h7WdMBIW/q/++tGd+emQr0XMdeZ6icnT/74Xk9fnabWn+gZgpE+9V+gujL3hhJOoNrnDVY7tWdzc7NUTg==" + "@babel/helper-member-expression-to-functions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.0.0.tgz", + "integrity": "sha512-avo+lm/QmZlv27Zsi0xEor2fKcqWG56D5ae9dzklpIaY7cQMK5N8VSpaNVPPagiqmy7LrEjK1IWdGMOqPu5csg==", + "requires": { + "@babel/types": "^7.0.0" + } }, - "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=" + "@babel/helper-module-imports": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.0.0.tgz", + "integrity": "sha512-aP/hlLq01DWNEiDg4Jn23i+CXxW/owM4WpDLFUbpjxe4NS3BhLVZQ5i7E0ZrxuQ/vwekIeciyamgB1UIYxxM6A==", + "requires": { + "@babel/types": "^7.0.0" + } }, - "bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" + "@babel/helper-module-transforms": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.4.4.tgz", + "integrity": "sha512-3Z1yp8TVQf+B4ynN7WoHPKS8EkdTbgAEy0nU0rs/1Kw4pDgmvYH3rz3aI11KgxKCba2cn7N+tqzV1mY2HMN96w==", + "requires": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/helper-simple-access": "^7.1.0", + "@babel/helper-split-export-declaration": "^7.4.4", + "@babel/template": "^7.4.4", + "@babel/types": "^7.4.4", + "lodash": "^4.17.11" + } }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + "@babel/helper-optimise-call-expression": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.0.0.tgz", + "integrity": "sha512-u8nd9NQePYNQV8iPWu/pLLYBqZBa4ZaY1YWRFMuxrid94wKI1QNt67NEZ7GAe5Kc/0LLScbim05xZFWkAdrj9g==", + "requires": { + "@babel/types": "^7.0.0" + } }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" + "@babel/helper-plugin-utils": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0.tgz", + "integrity": "sha512-CYAOUCARwExnEixLdB6sDm2dIJ/YgEAKDM1MOeMeZu9Ld/bDgVo8aiWrXwcY7OBh+1Ea2uUcVRcxKk0GJvW7QA==" }, - "combined-stream": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", - "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", + "@babel/helper-regex": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.4.4.tgz", + "integrity": "sha512-Y5nuB/kESmR3tKjU8Nkn1wMGEx1tjJX076HBMeL3XLQCu6vA/YRzuTW0bbb+qRnXvQGn+d6Rx953yffl8vEy7Q==", "requires": { - "delayed-stream": "~1.0.0" + "lodash": "^4.17.11" } }, - "content-disposition": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", - "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=" - }, - "content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" + "@babel/helper-remap-async-to-generator": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.1.0.tgz", + "integrity": "sha512-3fOK0L+Fdlg8S5al8u/hWE6vhufGSn0bN09xm2LXMy//REAF8kDCrYoOBKYmA8m5Nom+sV9LyLCwrFynA8/slg==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.0.0", + "@babel/helper-wrap-function": "^7.1.0", + "@babel/template": "^7.1.0", + "@babel/traverse": "^7.1.0", + "@babel/types": "^7.0.0" + } }, - "cookie": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", - "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" + "@babel/helper-replace-supers": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.4.4.tgz", + "integrity": "sha512-04xGEnd+s01nY1l15EuMS1rfKktNF+1CkKmHoErDppjAAZL+IUBZpzT748x262HF7fibaQPhbvWUl5HeSt1EXg==", + "requires": { + "@babel/helper-member-expression-to-functions": "^7.0.0", + "@babel/helper-optimise-call-expression": "^7.0.0", + "@babel/traverse": "^7.4.4", + "@babel/types": "^7.4.4" + } }, - "cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + "@babel/helper-simple-access": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.1.0.tgz", + "integrity": "sha512-Vk+78hNjRbsiu49zAPALxTb+JUQCz1aolpd8osOF16BGnLtseD21nbHgLPGUwrXEurZgiCOUmvs3ExTu4F5x6w==", + "requires": { + "@babel/template": "^7.1.0", + "@babel/types": "^7.0.0" + } }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + "@babel/helper-split-export-declaration": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.4.tgz", + "integrity": "sha512-Ro/XkzLf3JFITkW6b+hNxzZ1n5OQ80NvIUdmHspih1XAhtN3vPTuUFT4eQnela+2MaZ5ulH+iyP513KJrxbN7Q==", + "requires": { + "@babel/types": "^7.4.4" + } }, - "cors": { - "version": "2.8.4", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.4.tgz", - "integrity": "sha1-K9OB8usgECAQXNUOpZ2mMJBpRoY=", + "@babel/helper-wrap-function": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.2.0.tgz", + "integrity": "sha512-o9fP1BZLLSrYlxYEYyl2aS+Flun5gtjTIG8iln+XuEzQTs0PLagAGSXUcqruJwD5fM48jzIEggCKpIfWTcR7pQ==", "requires": { - "object-assign": "^4", - "vary": "^1" + "@babel/helper-function-name": "^7.1.0", + "@babel/template": "^7.1.0", + "@babel/traverse": "^7.1.0", + "@babel/types": "^7.2.0" } }, - "crc": { - "version": "3.4.4", - "resolved": "https://registry.npmjs.org/crc/-/crc-3.4.4.tgz", - "integrity": "sha1-naHpgOO9RPxck79as9ozeNheRms=" + "@babel/helpers": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.4.4.tgz", + "integrity": "sha512-igczbR/0SeuPR8RFfC7tGrbdTbFL3QTvH6D+Z6zNxnTe//GyqmtHmDkzrqDmyZ3eSwPqB/LhyKoU5DXsp+Vp2A==", + "requires": { + "@babel/template": "^7.4.4", + "@babel/traverse": "^7.4.4", + "@babel/types": "^7.4.4" + } }, - "dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "@babel/highlight": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz", + "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", "requires": { - "assert-plus": "^1.0.0" + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^4.0.0" } }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "@babel/node": { + "version": "7.4.5", + "resolved": "https://registry.npmjs.org/@babel/node/-/node-7.4.5.tgz", + "integrity": "sha512-nDXPT0KwYMycDHhFG9wKlkipCR+iXzzoX9bD2aF2UABLhQ13AKhNi5Y61W8ASGPPll/7p9GrHesmlOgTUJVcfw==", "requires": { - "ms": "2.0.0" + "@babel/polyfill": "^7.0.0", + "@babel/register": "^7.0.0", + "commander": "^2.8.1", + "lodash": "^4.17.11", + "node-environment-flags": "^1.0.5", + "v8flags": "^3.1.1" } }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + "@babel/parser": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.4.4.tgz", + "integrity": "sha512-5pCS4mOsL+ANsFZGdvNLybx4wtqAZJ0MJjMHxvzI3bvIsz6sQvzW8XX92EYIkiPtIvcfG3Aj+Ir5VNyjnZhP7w==" }, - "depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + "@babel/plugin-proposal-async-generator-functions": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.2.0.tgz", + "integrity": "sha512-+Dfo/SCQqrwx48ptLVGLdE39YtWRuKc/Y9I5Fy0P1DDBB9lsAHpjcEJQt+4IifuSOSTLBKJObJqMvaO1pIE8LQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-remap-async-to-generator": "^7.1.0", + "@babel/plugin-syntax-async-generators": "^7.2.0" + } }, - "destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + "@babel/plugin-proposal-json-strings": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.2.0.tgz", + "integrity": "sha512-MAFV1CA/YVmYwZG0fBQyXhmj0BHCB5egZHCKWIFVv/XCxAeVGIHfos3SwDck4LvCllENIAg7xMKOG5kH0dzyUg==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-json-strings": "^7.2.0" + } }, - "dotenv": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-6.0.0.tgz", - "integrity": "sha512-FlWbnhgjtwD+uNLUGHbMykMOYQaTivdHEmYwAKFjn6GKe/CqY0fNae93ZHTd20snh9ZLr8mTzIL9m0APQ1pjQg==" + "@babel/plugin-proposal-object-rest-spread": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.4.4.tgz", + "integrity": "sha512-dMBG6cSPBbHeEBdFXeQ2QLc5gUpg4Vkaz8octD4aoW/ISO+jBOcsuxYL7bsb5WSu8RLP6boxrBIALEHgoHtO9g==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-object-rest-spread": "^7.2.0" + } }, - "ecc-jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", - "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", - "optional": true, + "@babel/plugin-proposal-optional-catch-binding": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.2.0.tgz", + "integrity": "sha512-mgYj3jCcxug6KUcX4OBoOJz3CMrwRfQELPQ5560F70YQUBZB7uac9fqaWamKR1iWUzGiK2t0ygzjTScZnVz75g==", "requires": { - "jsbn": "~0.1.0" + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-optional-catch-binding": "^7.2.0" } }, - "ecdsa-sig-formatter": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.10.tgz", - "integrity": "sha1-HFlQAPBKiJffuFAAiSoPTDOvhsM=", + "@babel/plugin-proposal-unicode-property-regex": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.4.4.tgz", + "integrity": "sha512-j1NwnOqMG9mFUOH58JTFsA/+ZYzQLUZ/drqWUqxCYLGeu2JFZL8YrNC9hBxKmWtAuOCHPcRpgv7fhap09Fb4kA==", "requires": { - "safe-buffer": "^5.0.1" + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-regex": "^7.4.4", + "regexpu-core": "^4.5.4" } }, - "ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + "@babel/plugin-syntax-async-generators": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.2.0.tgz", + "integrity": "sha512-1ZrIRBv2t0GSlcwVoQ6VgSLpLgiN/FVQUzt9znxo7v2Ov4jJrs8RY8tv0wvDmFN3qIdMKWrmMMW6yZ0G19MfGg==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } }, - "ejs": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.6.1.tgz", - "integrity": "sha512-0xy4A/twfrRCnkhfk8ErDi5DqdAsAqeGxht4xkCUrsvhhbQNs7E+4jV0CN7+NKIY0aHE72+XvqtBIXzD31ZbXQ==" + "@babel/plugin-syntax-json-strings": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.2.0.tgz", + "integrity": "sha512-5UGYnMSLRE1dqqZwug+1LISpA403HzlSfsg6P9VXU6TBjcSHeNlw4DxDx7LgpF+iKZoOG/+uzqoRHTdcUpiZNg==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } }, - "encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" + "@babel/plugin-syntax-object-rest-spread": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.2.0.tgz", + "integrity": "sha512-t0JKGgqk2We+9may3t0xDdmneaXmyxq0xieYcKHxIsrJO64n1OiMWNUtc5gQK1PA0NpdCRrtZp4z+IUaKugrSA==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } }, - "errorhandler": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/errorhandler/-/errorhandler-1.5.0.tgz", - "integrity": "sha1-6rpkyl1UKjEayUX1gt78M2Fl2fQ=", + "@babel/plugin-syntax-optional-catch-binding": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.2.0.tgz", + "integrity": "sha512-bDe4xKNhb0LI7IvZHiA13kff0KEfaGX/Hv4lMA9+7TEc63hMNvfKo6ZFpXhKuEp+II/q35Gc4NoMeDZyaUbj9w==", "requires": { - "accepts": "~1.3.3", - "escape-html": "~1.0.3" + "@babel/helper-plugin-utils": "^7.0.0" } }, - "escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + "@babel/plugin-transform-arrow-functions": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.2.0.tgz", + "integrity": "sha512-ER77Cax1+8/8jCB9fo4Ud161OZzWN5qawi4GusDuRLcDbDG+bIGYY20zb2dfAFdTRGzrfq2xZPvF0R64EHnimg==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } }, - "etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" + "@babel/plugin-transform-async-to-generator": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.4.4.tgz", + "integrity": "sha512-YiqW2Li8TXmzgbXw+STsSqPBPFnGviiaSp6CYOq55X8GQ2SGVLrXB6pNid8HkqkZAzOH6knbai3snhP7v0fNwA==", + "requires": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-remap-async-to-generator": "^7.1.0" + } }, - "express": { - "version": "4.16.3", - "resolved": "https://registry.npmjs.org/express/-/express-4.16.3.tgz", - "integrity": "sha1-avilAjUNsyRuzEvs9rWjTSL37VM=", + "@babel/plugin-transform-block-scoped-functions": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.2.0.tgz", + "integrity": "sha512-ntQPR6q1/NKuphly49+QiQiTN0O63uOwjdD6dhIjSWBI5xlrbUFh720TIpzBhpnrLfv2tNH/BXvLIab1+BAI0w==", "requires": { - "accepts": "~1.3.5", - "array-flatten": "1.1.1", - "body-parser": "1.18.2", - "content-disposition": "0.5.2", - "content-type": "~1.0.4", - "cookie": "0.3.1", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "~1.1.2", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.1.1", - "fresh": "0.5.2", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "~2.3.0", - "parseurl": "~1.3.2", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.3", - "qs": "6.5.1", - "range-parser": "~1.2.0", - "safe-buffer": "5.1.1", - "send": "0.16.2", - "serve-static": "1.13.2", - "setprototypeof": "1.1.0", - "statuses": "~1.4.0", - "type-is": "~1.6.16", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "dependencies": { - "body-parser": { - "version": "1.18.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz", - "integrity": "sha1-h2eKGdhLR9hZuDGZvVm84iKxBFQ=", - "requires": { - "bytes": "3.0.0", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "~1.1.1", - "http-errors": "~1.6.2", - "iconv-lite": "0.4.19", - "on-finished": "~2.3.0", - "qs": "6.5.1", - "raw-body": "2.3.2", - "type-is": "~1.6.15" - } - }, - "iconv-lite": { - "version": "0.4.19", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", - "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==" - }, - "qs": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", - "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==" - }, - "raw-body": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz", - "integrity": "sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=", - "requires": { - "bytes": "3.0.0", - "http-errors": "1.6.2", - "iconv-lite": "0.4.19", - "unpipe": "1.0.0" - }, - "dependencies": { - "depd": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", - "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=" - }, - "http-errors": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", - "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=", - "requires": { - "depd": "1.1.1", - "inherits": "2.0.3", - "setprototypeof": "1.0.3", - "statuses": ">= 1.3.1 < 2" - } - }, - "setprototypeof": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", - "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=" - } - } - }, - "statuses": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", - "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" - } + "@babel/helper-plugin-utils": "^7.0.0" } }, - "express-jwt": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/express-jwt/-/express-jwt-5.3.1.tgz", - "integrity": "sha512-1C9RNq0wMp/JvsH/qZMlg3SIPvKu14YkZ4YYv7gJQ1Vq+Dv8LH9tLKenS5vMNth45gTlEUGx+ycp9IHIlaHP/g==", + "@babel/plugin-transform-block-scoping": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.4.4.tgz", + "integrity": "sha512-jkTUyWZcTrwxu5DD4rWz6rDB5Cjdmgz6z7M7RLXOJyCUkFBawssDGcGh8M/0FTSB87avyJI1HsTwUXp9nKA1PA==", "requires": { - "async": "^1.5.0", - "express-unless": "^0.3.0", - "jsonwebtoken": "^8.1.0", - "lodash.set": "^4.0.0" + "@babel/helper-plugin-utils": "^7.0.0", + "lodash": "^4.17.11" } }, - "express-session": { - "version": "1.15.6", - "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.15.6.tgz", - "integrity": "sha512-r0nrHTCYtAMrFwZ0kBzZEXa1vtPVrw0dKvGSrKP4dahwBQ1BJpF2/y1Pp4sCD/0kvxV4zZeclyvfmw0B4RMJQA==", + "@babel/plugin-transform-classes": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.4.4.tgz", + "integrity": "sha512-/e44eFLImEGIpL9qPxSRat13I5QNRgBLu2hOQJCF7VLy/otSM/sypV1+XaIw5+502RX/+6YaSAPmldk+nhHDPw==", "requires": { - "cookie": "0.3.1", - "cookie-signature": "1.0.6", - "crc": "3.4.4", - "debug": "2.6.9", - "depd": "~1.1.1", - "on-headers": "~1.0.1", - "parseurl": "~1.3.2", - "uid-safe": "~2.1.5", - "utils-merge": "1.0.1" + "@babel/helper-annotate-as-pure": "^7.0.0", + "@babel/helper-define-map": "^7.4.4", + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-optimise-call-expression": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-replace-supers": "^7.4.4", + "@babel/helper-split-export-declaration": "^7.4.4", + "globals": "^11.1.0" } }, - "express-unless": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/express-unless/-/express-unless-0.3.1.tgz", - "integrity": "sha1-JVfBRudb65A+LSR/m1ugFFJpbiA=" + "@babel/plugin-transform-computed-properties": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.2.0.tgz", + "integrity": "sha512-kP/drqTxY6Xt3NNpKiMomfgkNn4o7+vKxK2DDKcBG9sHj51vHqMBGy8wbDS/J4lMxnqs153/T3+DmCEAkC5cpA==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } }, - "extend": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", - "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=" + "@babel/plugin-transform-destructuring": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.4.4.tgz", + "integrity": "sha512-/aOx+nW0w8eHiEHm+BTERB2oJn5D127iye/SUQl7NjHy0lf+j7h4MKMMSOwdazGq9OxgiNADncE+SRJkCxjZpQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } }, - "extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + "@babel/plugin-transform-dotall-regex": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.4.4.tgz", + "integrity": "sha512-P05YEhRc2h53lZDjRPk/OektxCVevFzZs2Gfjd545Wde3k+yFDbXORgl2e0xpbq8mLcKJ7Idss4fAg0zORN/zg==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-regex": "^7.4.4", + "regexpu-core": "^4.5.4" + } }, - "fast-deep-equal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", - "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=" + "@babel/plugin-transform-duplicate-keys": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.2.0.tgz", + "integrity": "sha512-q+yuxW4DsTjNceUiTzK0L+AfQ0zD9rWaTLiUqHA8p0gxx7lu1EylenfzjeIWNkPy6e/0VG/Wjw9uf9LueQwLOw==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } }, - "fast-json-stable-stringify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", - "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" + "@babel/plugin-transform-exponentiation-operator": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.2.0.tgz", + "integrity": "sha512-umh4hR6N7mu4Elq9GG8TOu9M0bakvlsREEC+ialrQN6ABS4oDQ69qJv1VtR3uxlKMCQMCvzk7vr17RHKcjx68A==", + "requires": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.1.0", + "@babel/helper-plugin-utils": "^7.0.0" + } }, - "finalhandler": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", - "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", + "@babel/plugin-transform-for-of": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.4.4.tgz", + "integrity": "sha512-9T/5Dlr14Z9TIEXLXkt8T1DU7F24cbhwhMNUziN3hB1AXoZcdzPcTiKGRn/6iOymDqtTKWnr/BtRKN9JwbKtdQ==", "requires": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "~2.3.0", - "parseurl": "~1.3.2", - "statuses": "~1.4.0", - "unpipe": "~1.0.0" - }, - "dependencies": { - "statuses": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", - "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" - } + "@babel/helper-plugin-utils": "^7.0.0" } }, - "forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + "@babel/plugin-transform-function-name": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.4.4.tgz", + "integrity": "sha512-iU9pv7U+2jC9ANQkKeNF6DrPy4GBa4NWQtl6dHB4Pb3izX2JOEvDTFarlNsBj/63ZEzNNIAMs3Qw4fNCcSOXJA==", + "requires": { + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-plugin-utils": "^7.0.0" + } }, - "form-data": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz", - "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", + "@babel/plugin-transform-literals": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.2.0.tgz", + "integrity": "sha512-2ThDhm4lI4oV7fVQ6pNNK+sx+c/GM5/SaML0w/r4ZB7sAneD/piDJtwdKlNckXeyGK7wlwg2E2w33C/Hh+VFCg==", "requires": { - "asynckit": "^0.4.0", - "combined-stream": "1.0.6", - "mime-types": "^2.1.12" + "@babel/helper-plugin-utils": "^7.0.0" } }, - "forwarded": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", - "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" + "@babel/plugin-transform-member-expression-literals": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.2.0.tgz", + "integrity": "sha512-HiU3zKkSU6scTidmnFJ0bMX8hz5ixC93b4MHMiYebmk2lUVNGOboPsqQvx5LzooihijUoLR/v7Nc1rbBtnc7FA==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } }, - "fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" + "@babel/plugin-transform-modules-amd": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.2.0.tgz", + "integrity": "sha512-mK2A8ucqz1qhrdqjS9VMIDfIvvT2thrEsIQzbaTdc5QFzhDjQv2CkJJ5f6BXIkgbmaoax3zBr2RyvV/8zeoUZw==", + "requires": { + "@babel/helper-module-transforms": "^7.1.0", + "@babel/helper-plugin-utils": "^7.0.0" + } }, - "getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "@babel/plugin-transform-modules-commonjs": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.4.4.tgz", + "integrity": "sha512-4sfBOJt58sEo9a2BQXnZq+Q3ZTSAUXyK3E30o36BOGnJ+tvJ6YSxF0PG6kERvbeISgProodWuI9UVG3/FMY6iw==", "requires": { - "assert-plus": "^1.0.0" + "@babel/helper-module-transforms": "^7.4.4", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-simple-access": "^7.1.0" } }, - "har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" + "@babel/plugin-transform-modules-systemjs": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.4.4.tgz", + "integrity": "sha512-MSiModfILQc3/oqnG7NrP1jHaSPryO6tA2kOMmAQApz5dayPxWiHqmq4sWH2xF5LcQK56LlbKByCd8Aah/OIkQ==", + "requires": { + "@babel/helper-hoist-variables": "^7.4.4", + "@babel/helper-plugin-utils": "^7.0.0" + } }, - "har-validator": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", - "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", + "@babel/plugin-transform-modules-umd": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.2.0.tgz", + "integrity": "sha512-BV3bw6MyUH1iIsGhXlOK6sXhmSarZjtJ/vMiD9dNmpY8QXFFQTj+6v92pcfy1iqa8DeAfJFwoxcrS/TUZda6sw==", "requires": { - "ajv": "^5.1.0", - "har-schema": "^2.0.0" + "@babel/helper-module-transforms": "^7.1.0", + "@babel/helper-plugin-utils": "^7.0.0" } }, - "http-errors": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", - "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", + "@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.4.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.4.5.tgz", + "integrity": "sha512-z7+2IsWafTBbjNsOxU/Iv5CvTJlr5w4+HGu1HovKYTtgJ362f7kBcQglkfmlspKKZ3bgrbSGvLfNx++ZJgCWsg==", "requires": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" + "regexp-tree": "^0.1.6" } }, - "http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "@babel/plugin-transform-new-target": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.4.4.tgz", + "integrity": "sha512-r1z3T2DNGQwwe2vPGZMBNjioT2scgWzK9BCnDEh+46z8EEwXBq24uRzd65I7pjtugzPSj921aM15RpESgzsSuA==", "requires": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" + "@babel/helper-plugin-utils": "^7.0.0" } }, - "iconv-lite": { - "version": "0.4.23", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", - "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", + "@babel/plugin-transform-object-super": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.2.0.tgz", + "integrity": "sha512-VMyhPYZISFZAqAPVkiYb7dUe2AsVi2/wCT5+wZdsNO31FojQJa9ns40hzZ6U9f50Jlq4w6qwzdBB2uwqZ00ebg==", "requires": { - "safer-buffer": ">= 2.1.2 < 3" + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-replace-supers": "^7.1.0" } }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + "@babel/plugin-transform-parameters": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.4.4.tgz", + "integrity": "sha512-oMh5DUO1V63nZcu/ZVLQFqiihBGo4OpxJxR1otF50GMeCLiRx5nUdtokd+u9SuVJrvvuIh9OosRFPP4pIPnwmw==", + "requires": { + "@babel/helper-call-delegate": "^7.4.4", + "@babel/helper-get-function-arity": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0" + } }, - "ipaddr.js": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.6.0.tgz", - "integrity": "sha1-4/o1e3c9phnybpXwSdBVxyeW+Gs=" + "@babel/plugin-transform-property-literals": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.2.0.tgz", + "integrity": "sha512-9q7Dbk4RhgcLp8ebduOpCbtjh7C0itoLYHXd9ueASKAG/is5PQtMR5VJGka9NKqGhYEGn5ITahd4h9QeBMylWQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + "@babel/plugin-transform-regenerator": { + "version": "7.4.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.4.5.tgz", + "integrity": "sha512-gBKRh5qAaCWntnd09S8QC7r3auLCqq5DI6O0DlfoyDjslSBVqBibrMdsqO+Uhmx3+BlOmE/Kw1HFxmGbv0N9dA==", + "requires": { + "regenerator-transform": "^0.14.0" + } }, - "isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + "@babel/plugin-transform-reserved-words": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.2.0.tgz", + "integrity": "sha512-fz43fqW8E1tAB3DKF19/vxbpib1fuyCwSPE418ge5ZxILnBhWyhtPgz8eh1RCGGJlwvksHkyxMxh0eenFi+kFw==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } }, - "jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "optional": true + "@babel/plugin-transform-shorthand-properties": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.2.0.tgz", + "integrity": "sha512-QP4eUM83ha9zmYtpbnyjTLAGKQritA5XW/iG9cjtuOI8s1RuL/3V6a3DeSHfKutJQ+ayUfeZJPcnCYEQzaPQqg==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } }, - "json-schema": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + "@babel/plugin-transform-spread": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.2.2.tgz", + "integrity": "sha512-KWfky/58vubwtS0hLqEnrWJjsMGaOeSBn90Ezn5Jeg9Z8KKHmELbP1yGylMlm5N6TPKeY9A2+UaSYLdxahg01w==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } }, - "json-schema-traverse": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", - "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=" + "@babel/plugin-transform-sticky-regex": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.2.0.tgz", + "integrity": "sha512-KKYCoGaRAf+ckH8gEL3JHUaFVyNHKe3ASNsZ+AlktgHevvxGigoIttrEJb8iKN03Q7Eazlv1s6cx2B2cQ3Jabw==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-regex": "^7.0.0" + } }, - "json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + "@babel/plugin-transform-template-literals": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.4.4.tgz", + "integrity": "sha512-mQrEC4TWkhLN0z8ygIvEL9ZEToPhG5K7KDW3pzGqOfIGZ28Jb0POUkeWcoz8HnHvhFy6dwAT1j8OzqN8s804+g==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0" + } }, - "jsonwebtoken": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.3.0.tgz", - "integrity": "sha512-oge/hvlmeJCH+iIz1DwcO7vKPkNGJHhgkspk8OH3VKlw+mbi42WtD4ig1+VXRln765vxptAv+xT26Fd3cteqag==", + "@babel/plugin-transform-typeof-symbol": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.2.0.tgz", + "integrity": "sha512-2LNhETWYxiYysBtrBTqL8+La0jIoQQnIScUJc74OYvUGRmkskNY4EzLCnjHBzdmb38wqtTaixpo1NctEcvMDZw==", "requires": { - "jws": "^3.1.5", - "lodash.includes": "^4.3.0", - "lodash.isboolean": "^3.0.3", - "lodash.isinteger": "^4.0.4", - "lodash.isnumber": "^3.0.3", - "lodash.isplainobject": "^4.0.6", - "lodash.isstring": "^4.0.1", - "lodash.once": "^4.0.0", - "ms": "^2.1.1" + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-unicode-regex": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.4.4.tgz", + "integrity": "sha512-il+/XdNw01i93+M9J9u4T7/e/Ue/vWfNZE4IRUQjplu2Mqb/AFTDimkw2tdEdSH50wuQXZAbXSql0UphQke+vA==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-regex": "^7.4.4", + "regexpu-core": "^4.5.4" + } + }, + "@babel/polyfill": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/polyfill/-/polyfill-7.4.4.tgz", + "integrity": "sha512-WlthFLfhQQhh+A2Gn5NSFl0Huxz36x86Jn+E9OW7ibK8edKPq+KLy4apM1yDpQ8kJOVi1OVjpP4vSDLdrI04dg==", + "requires": { + "core-js": "^2.6.5", + "regenerator-runtime": "^0.13.2" + } + }, + "@babel/preset-env": { + "version": "7.4.5", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.4.5.tgz", + "integrity": "sha512-f2yNVXM+FsR5V8UwcFeIHzHWgnhXg3NpRmy0ADvALpnhB0SLbCvrCRr4BLOUYbQNLS+Z0Yer46x9dJXpXewI7w==", + "requires": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-proposal-async-generator-functions": "^7.2.0", + "@babel/plugin-proposal-json-strings": "^7.2.0", + "@babel/plugin-proposal-object-rest-spread": "^7.4.4", + "@babel/plugin-proposal-optional-catch-binding": "^7.2.0", + "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", + "@babel/plugin-syntax-async-generators": "^7.2.0", + "@babel/plugin-syntax-json-strings": "^7.2.0", + "@babel/plugin-syntax-object-rest-spread": "^7.2.0", + "@babel/plugin-syntax-optional-catch-binding": "^7.2.0", + "@babel/plugin-transform-arrow-functions": "^7.2.0", + "@babel/plugin-transform-async-to-generator": "^7.4.4", + "@babel/plugin-transform-block-scoped-functions": "^7.2.0", + "@babel/plugin-transform-block-scoping": "^7.4.4", + "@babel/plugin-transform-classes": "^7.4.4", + "@babel/plugin-transform-computed-properties": "^7.2.0", + "@babel/plugin-transform-destructuring": "^7.4.4", + "@babel/plugin-transform-dotall-regex": "^7.4.4", + "@babel/plugin-transform-duplicate-keys": "^7.2.0", + "@babel/plugin-transform-exponentiation-operator": "^7.2.0", + "@babel/plugin-transform-for-of": "^7.4.4", + "@babel/plugin-transform-function-name": "^7.4.4", + "@babel/plugin-transform-literals": "^7.2.0", + "@babel/plugin-transform-member-expression-literals": "^7.2.0", + "@babel/plugin-transform-modules-amd": "^7.2.0", + "@babel/plugin-transform-modules-commonjs": "^7.4.4", + "@babel/plugin-transform-modules-systemjs": "^7.4.4", + "@babel/plugin-transform-modules-umd": "^7.2.0", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.4.5", + "@babel/plugin-transform-new-target": "^7.4.4", + "@babel/plugin-transform-object-super": "^7.2.0", + "@babel/plugin-transform-parameters": "^7.4.4", + "@babel/plugin-transform-property-literals": "^7.2.0", + "@babel/plugin-transform-regenerator": "^7.4.5", + "@babel/plugin-transform-reserved-words": "^7.2.0", + "@babel/plugin-transform-shorthand-properties": "^7.2.0", + "@babel/plugin-transform-spread": "^7.2.0", + "@babel/plugin-transform-sticky-regex": "^7.2.0", + "@babel/plugin-transform-template-literals": "^7.4.4", + "@babel/plugin-transform-typeof-symbol": "^7.2.0", + "@babel/plugin-transform-unicode-regex": "^7.4.4", + "@babel/types": "^7.4.4", + "browserslist": "^4.6.0", + "core-js-compat": "^3.1.1", + "invariant": "^2.2.2", + "js-levenshtein": "^1.1.3", + "semver": "^5.5.0" + } + }, + "@babel/register": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.4.4.tgz", + "integrity": "sha512-sn51H88GRa00+ZoMqCVgOphmswG4b7mhf9VOB0LUBAieykq2GnRFerlN+JQkO/ntT7wz4jaHNSRPg9IdMPEUkA==", + "requires": { + "core-js": "^3.0.0", + "find-cache-dir": "^2.0.0", + "lodash": "^4.17.11", + "mkdirp": "^0.5.1", + "pirates": "^4.0.0", + "source-map-support": "^0.5.9" + }, + "dependencies": { + "core-js": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.1.0.tgz", + "integrity": "sha512-EyF8cMvUWRDWRLmC3+i50D1DqK4aFZWb/6PDPP2QfX2r0zXkgR2V9wt7jX7TRM0Qdj/3f6+JQkqfCaGkSj92iQ==" + } + } + }, + "@babel/template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.4.4.tgz", + "integrity": "sha512-CiGzLN9KgAvgZsnivND7rkA+AeJ9JB0ciPOD4U59GKbQP2iQl+olF1l76kJOupqidozfZ32ghwBEJDhnk9MEcw==", + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/parser": "^7.4.4", + "@babel/types": "^7.4.4" + } + }, + "@babel/traverse": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.4.4.tgz", + "integrity": "sha512-Gw6qqkw/e6AGzlyj9KnkabJX7VcubqPtkUQVAwkc0wUMldr3A/hezNB3Rc5eIvId95iSGkGIOe5hh1kMKf951A==", + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/generator": "^7.4.4", + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-split-export-declaration": "^7.4.4", + "@babel/parser": "^7.4.4", + "@babel/types": "^7.4.4", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.11" }, "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + }, "ms": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", @@ -575,503 +787,9968 @@ } } }, - "jsprim": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", - "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "@babel/types": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.4.4.tgz", + "integrity": "sha512-dOllgYdnEFOebhkKCjzSVFqw/PmmB8pH6RGOWkY4GsboQNd47b1fBThBSwlHAq9alF9vc1M3+6oqR47R50L0tQ==", "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.2.3", - "verror": "1.10.0" + "esutils": "^2.0.2", + "lodash": "^4.17.11", + "to-fast-properties": "^2.0.0" } }, - "jwa": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.1.6.tgz", - "integrity": "sha512-tBO/cf++BUsJkYql/kBbJroKOgHWEigTKBAjjBEmrMGYd1QMBC74Hr4Wo2zCZw6ZrVhlJPvoMrkcOnlWR/DJfw==", + "@hapi/address": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@hapi/address/-/address-2.0.0.tgz", + "integrity": "sha512-mV6T0IYqb0xL1UALPFplXYQmR0twnXG0M6jUswpquqT2sD12BOiCiLy3EvMp/Fy7s3DZElC4/aPjEjo2jeZpvw==" + }, + "@hapi/hoek": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-6.2.3.tgz", + "integrity": "sha512-CtV9cp35+6Sfh6OfB+AYBozNIorZ6npNJjfO8InIyh/iFQI7uBW9bIApYoYf6TWq9w9BArecw2DDJf7oK+VlRw==" + }, + "@hapi/joi": { + "version": "15.0.3", + "resolved": "https://registry.npmjs.org/@hapi/joi/-/joi-15.0.3.tgz", + "integrity": "sha512-z6CesJ2YBwgVCi+ci8SI8zixoj8bGFn/vZb9MBPbSyoxsS2PnWYjHcyTM17VLK6tx64YVK38SDIh10hJypB+ig==", "requires": { - "buffer-equal-constant-time": "1.0.1", - "ecdsa-sig-formatter": "1.0.10", - "safe-buffer": "^5.0.1" + "@hapi/address": "2.x.x", + "@hapi/hoek": "6.x.x", + "@hapi/topo": "3.x.x" } }, - "jws": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/jws/-/jws-3.1.5.tgz", - "integrity": "sha512-GsCSexFADNQUr8T5HPJvayTjvPIfoyJPtLQBwn5a4WZQchcrPMPMAWcC1AzJVRDKyD6ZPROPAxgv6rfHViO4uQ==", + "@hapi/topo": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-3.1.0.tgz", + "integrity": "sha512-gZDI/eXOIk8kP2PkUKjWu9RW8GGVd2Hkgjxyr/S7Z+JF+0mr7bAlbw+DkTRxnD580o8Kqxlnba9wvqp5aOHBww==", "requires": { - "jwa": "^1.1.5", - "safe-buffer": "^5.0.1" + "@hapi/hoek": "6.x.x" } }, - "kareem": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.2.1.tgz", - "integrity": "sha512-xpDFy8OxkFM+vK6pXy6JmH92ibeEFUuDWzas5M9L7MzVmHW3jzwAHxodCPV/BYkf4A31bVDLyonrMfp9RXb/oA==" + "@sendgrid/client": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@sendgrid/client/-/client-6.4.0.tgz", + "integrity": "sha512-GcO+hKXMQiwN0xMGfPITArlj4Nab1vZsrsRLmsJlcXGZV1V1zQC6XuAWJv6MGDd0hr/jKaXmCJ1XMYkxIRQHFw==", + "requires": { + "@sendgrid/helpers": "^6.4.0", + "@types/request": "^2.0.3", + "request": "^2.88.0" + }, + "dependencies": { + "ajv": { + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz", + "integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==", + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "aws4": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" + }, + "har-validator": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "requires": { + "ajv": "^6.5.5", + "har-schema": "^2.0.0" + } + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "mime-db": { + "version": "1.40.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", + "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==" + }, + "mime-types": { + "version": "2.1.24", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", + "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", + "requires": { + "mime-db": "1.40.0" + } + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" + }, + "request": { + "version": "2.88.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", + "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "tough-cookie": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "requires": { + "psl": "^1.1.24", + "punycode": "^1.4.1" + } + } + } }, - "lodash": { - "version": "4.17.10", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", - "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==" + "@sendgrid/helpers": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@sendgrid/helpers/-/helpers-6.4.0.tgz", + "integrity": "sha512-1dDDXauArHyxwTKFFfWvQpsijmwalyLgwoQJ3FRCssFq1RfqYDgFhRg0Xs3v/IXS2jkKWePSWiPORSR4Sysdpw==", + "requires": { + "chalk": "^2.0.1", + "deepmerge": "^2.1.1" + } }, - "lodash.foreach": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.foreach/-/lodash.foreach-4.5.0.tgz", - "integrity": "sha1-Gmo16s5AEoDH8G3d7DUWWrJ+PlM=" + "@sendgrid/mail": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@sendgrid/mail/-/mail-6.4.0.tgz", + "integrity": "sha512-pVzbqbxhZ4FUN6iSIksRLtyXRPurrcee1i0noPDStDCLlHVwUR+TofeeKIFWGpIvbbk5UR6S6iV/U5ie8Kdblw==", + "requires": { + "@sendgrid/client": "^6.4.0", + "@sendgrid/helpers": "^6.4.0" + } }, - "lodash.get": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", - "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=" + "@sinonjs/commons": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.4.0.tgz", + "integrity": "sha512-9jHK3YF/8HtJ9wCAbG+j8cD0i0+ATS9A7gXFqS36TblLPNy6rEEc+SB0imo91eCboGaBYGV/MT1/br/J+EE7Tw==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } }, - "lodash.includes": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", - "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" + "@sinonjs/formatio": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-3.2.1.tgz", + "integrity": "sha512-tsHvOB24rvyvV2+zKMmPkZ7dXX6LSLKZ7aOtXY6Edklp0uRcgGpOsQTTGTcWViFyx4uhWc6GV8QdnALbIbIdeQ==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1", + "@sinonjs/samsam": "^3.1.0" + } }, - "lodash.isboolean": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", - "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" + "@sinonjs/samsam": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-3.3.1.tgz", + "integrity": "sha512-wRSfmyd81swH0hA1bxJZJ57xr22kC07a1N4zuIL47yTS04bDk6AoCkczcqHEjcRPmJ+FruGJ9WBQiJwMtIElFw==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.0.2", + "array-from": "^2.1.1", + "lodash": "^4.17.11" + } }, - "lodash.isinteger": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", - "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" + "@sinonjs/text-encoding": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz", + "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", + "dev": true + }, + "@types/caseless": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.2.tgz", + "integrity": "sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w==" + }, + "@types/chai": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.1.7.tgz", + "integrity": "sha512-2Y8uPt0/jwjhQ6EiluT0XCri1Dbplr0ZxfFXUz+ye13gaqE8u5gL5ppao1JrUYr9cIip5S6MvQzBS7Kke7U9VA==", + "dev": true + }, + "@types/cookiejar": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.1.tgz", + "integrity": "sha512-aRnpPa7ysx3aNW60hTiCtLHlQaIFsXFCgQlpakNgDNVFzbtusSY8PwjAQgRWfSk0ekNoBjO51eQRB6upA9uuyw==", + "dev": true + }, + "@types/form-data": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@types/form-data/-/form-data-2.2.1.tgz", + "integrity": "sha512-JAMFhOaHIciYVh8fb5/83nmuO/AHwmto+Hq7a9y8FzLDcC1KCU344XDOMEmahnrTFlHjgh4L0WJFczNIX2GxnQ==", + "requires": { + "@types/node": "*" + } }, - "lodash.isnumber": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", - "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" + "@types/node": { + "version": "11.13.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-11.13.7.tgz", + "integrity": "sha512-suFHr6hcA9mp8vFrZTgrmqW2ZU3mbWsryQtQlY/QvwTISCw7nw/j+bCQPPohqmskhmqa5wLNuMHTTsc+xf1MQg==" }, - "lodash.isplainobject": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" + "@types/request": { + "version": "2.48.1", + "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.1.tgz", + "integrity": "sha512-ZgEZ1TiD+KGA9LiAAPPJL68Id2UWfeSO62ijSXZjFJArVV+2pKcsVHmrcu+1oiE3q6eDGiFiSolRc4JHoerBBg==", + "requires": { + "@types/caseless": "*", + "@types/form-data": "*", + "@types/node": "*", + "@types/tough-cookie": "*" + } }, - "lodash.isstring": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", - "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" - }, - "lodash.once": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" + "@types/superagent": { + "version": "3.8.7", + "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-3.8.7.tgz", + "integrity": "sha512-9KhCkyXv268A2nZ1Wvu7rQWM+BmdYUVkycFeNnYrUL5Zwu7o8wPQ3wBfW59dDP+wuoxw0ww8YKgTNv8j/cgscA==", + "dev": true, + "requires": { + "@types/cookiejar": "*", + "@types/node": "*" + } }, - "lodash.set": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz", - "integrity": "sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM=" + "@types/tough-cookie": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-2.3.5.tgz", + "integrity": "sha512-SCcK7mvGi3+ZNz833RRjFIxrn4gI1PPR3NtuIS+6vMkvmsGjosqTJwRt5bAEFLRz+wtJMWv8+uOnZf2hi2QXTg==" }, - "media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + "URIjs": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/URIjs/-/URIjs-1.16.1.tgz", + "integrity": "sha1-7evGeLi3SyawXStIHhI4P1rgS4s=" }, - "merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" }, - "method-override": { - "version": "2.3.10", - "resolved": "https://registry.npmjs.org/method-override/-/method-override-2.3.10.tgz", - "integrity": "sha1-49r41d7hDdLc59SuiNYrvud0drQ=", + "accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", "requires": { - "debug": "2.6.9", - "methods": "~1.1.2", - "parseurl": "~1.3.2", - "vary": "~1.1.2" + "mime-types": "~2.1.24", + "negotiator": "0.6.2" + }, + "dependencies": { + "mime-db": { + "version": "1.40.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", + "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==" + }, + "mime-types": { + "version": "2.1.24", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", + "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", + "requires": { + "mime-db": "1.40.0" + } + } } }, - "methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" + "acorn": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.1.1.tgz", + "integrity": "sha512-jPTiwtOxaHNaAPg/dmrJ/beuzLRnXtB0kQPQ8JpotKJgTB6rX6c8mlf315941pyjBSaPg8NHXS9fhP4u17DpGA==", + "dev": true }, - "mime": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", - "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==" + "acorn-jsx": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.0.1.tgz", + "integrity": "sha512-HJ7CfNHrfJLlNTzIEUTj43LNWGkqpRLxm3YjAlcD0ACydk9XynzYsCBHxut+iqt+1aBXkx9UP/w/ZqMr13XIzg==", + "dev": true }, - "mime-db": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", - "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==" + "after": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz", + "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=" }, - "mime-types": { - "version": "2.1.18", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", - "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", + "ajv": { + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz", + "integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==", "requires": { - "mime-db": "~1.33.0" + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" } }, - "mongodb": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.1.1.tgz", - "integrity": "sha512-GU9oWK4pi8PC7NyGiwjFMwZyMqwGWoMEMvM0LZh7UKW/FFAqgmZKjjriD+5MEOCDUJE2dtHX93/K5UtDxO0otg==", + "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", + "integrity": "sha1-w2rsy6VjuJzrVW82kPCx2eNUf38=", "requires": { - "mongodb-core": "3.1.0" + "string-width": "^2.0.0" } }, - "mongodb-core": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mongodb-core/-/mongodb-core-3.1.0.tgz", - "integrity": "sha512-qRjG62Fu//CZhkgn0jA/k8jh5MhACIq8cOJUryH6sck87pgt+C222MSD02tsCq5zNo/B6ZFHtNodZ2qpf8E86g==", - "requires": { - "bson": "~1.0.4", - "require_optional": "^1.0.1", - "saslprep": "^1.0.0" - } - }, - "mongoose": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.2.2.tgz", - "integrity": "sha512-DpQGUHEUbKrxWGOU9uzKV1mtN/DFmXUjhYEzByqeQ2FwQeFWhXJdl+UPCsItqvFOwsFDMY1J6fxHOnWlOoHU8g==", - "requires": { - "async": "2.6.1", - "bson": "~1.0.5", - "kareem": "2.2.1", - "lodash.get": "4.4.2", - "mongodb": "3.1.1", - "mongodb-core": "3.1.0", - "mongoose-legacy-pluralize": "1.0.2", - "mpath": "0.4.1", - "mquery": "3.0.0", - "ms": "2.0.0", - "regexp-clone": "0.0.1", - "sliced": "1.0.1" + "ansi-colors": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", + "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", + "dev": true + }, + "ansi-escapes": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", + "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", + "dev": true + }, + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=" + }, + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "requires": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" }, "dependencies": { - "async": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.1.tgz", - "integrity": "sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==", + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", "requires": { - "lodash": "^4.17.10" + "remove-trailing-separator": "^1.0.1" } } } }, - "mongoose-legacy-pluralize": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/mongoose-legacy-pluralize/-/mongoose-legacy-pluralize-1.0.2.tgz", - "integrity": "sha512-Yo/7qQU4/EyIS8YDFSeenIvXxZN+ld7YdV9LqFVQJzTLye8unujAWPZ4NWKfFA+RNjh+wvTWKY9Z3E5XM6ZZiQ==" + "append-field": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", + "integrity": "sha1-HjRA6RXwsSA9I3SOeO3XubW0PlY=" }, - "mongoose-unique-validator": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mongoose-unique-validator/-/mongoose-unique-validator-2.0.1.tgz", - "integrity": "sha512-Eqq7lZMy0nPSojG8UyDZvlBie1aBZJXk68GDMBXXQH0TAi0hZHf76nCrwuipReNK1jLkjyKzV7eIZotja5eEBw==", + "append-transform": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-1.0.0.tgz", + "integrity": "sha512-P009oYkeHyU742iSZJzZZywj4QRJdnTWffaKuJQLablCZ1uz6/cW4yaRgcDaoQ+uwOxxnt0gRUcwfsNP2ri0gw==", + "dev": true, "requires": { - "lodash.foreach": "^4.1.0", - "lodash.get": "^4.0.2" + "default-require-extensions": "^2.0.0" } }, - "morgan": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.9.0.tgz", - "integrity": "sha1-0B+mxlhZt2/PMbPLU6OCGjEdgFE=", + "aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" + }, + "archy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", + "dev": true + }, + "are-we-there-yet": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", + "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", "requires": { - "basic-auth": "~2.0.0", - "debug": "2.6.9", - "depd": "~1.1.1", - "on-finished": "~2.3.0", - "on-headers": "~1.0.1" + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" } }, - "mpath": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.4.1.tgz", - "integrity": "sha512-NNY/MpBkALb9jJmjpBlIi6GRoLveLUM0pJzgbp9vY9F7IQEb/HREC/nxrixechcQwd1NevOhJnWWV8QQQRE+OA==" + "argh": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/argh/-/argh-0.1.4.tgz", + "integrity": "sha1-PrTWEpc/xrbcbvM49W91nyrFw6Y=" }, - "mquery": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mquery/-/mquery-3.0.0.tgz", - "integrity": "sha512-WL1Lk8v4l8VFSSwN3yCzY9TXw+fKVYKn6f+w86TRzOLSE8k1yTgGaLBPUByJQi8VcLbOdnUneFV/y3Kv874pnQ==", + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "requires": { - "bluebird": "3.5.0", - "debug": "2.6.9", - "regexp-clone": "0.0.1", - "sliced": "0.0.5" - }, - "dependencies": { - "sliced": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/sliced/-/sliced-0.0.5.tgz", - "integrity": "sha1-XtwETKTrb3gW1Qui/GPiXY/kcH8=" - } + "sprintf-js": "~1.0.2" } }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=" }, - "negotiator": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", - "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==" }, - "oauth-sign": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", - "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=" + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=" }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + "array-find-index": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", + "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=" }, - "on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + }, + "array-from": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/array-from/-/array-from-2.1.1.tgz", + "integrity": "sha1-z+nYwmYoudxa7MYqn12PHzUsEZU=", + "dev": true + }, + "array-includes": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.0.3.tgz", + "integrity": "sha1-GEtI9i2S10UrsxsyMWXH+L0CJm0=", + "dev": true, "requires": { - "ee-first": "1.1.1" + "define-properties": "^1.1.2", + "es-abstract": "^1.7.0" } }, - "on-headers": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.1.tgz", - "integrity": "sha1-ko9dD0cNSTQmUepnlLCFfBAGk/c=" + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=" }, - "parseurl": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", - "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=" + "arraybuffer.slice": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz", + "integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==" }, - "passport": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/passport/-/passport-0.4.0.tgz", - "integrity": "sha1-xQlWkTR71a07XhgCOMORTRbwWBE=", - "requires": { - "passport-strategy": "1.x.x", - "pause": "0.0.1" - } + "asn1": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", + "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=" }, - "passport-local": { + "assert-plus": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/passport-local/-/passport-local-1.0.0.tgz", - "integrity": "sha1-H+YyaMkudWBmJkN+O5BmYsFbpu4=", - "requires": { - "passport-strategy": "1.x.x" - } + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" }, - "passport-strategy": { + "assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true + }, + "assign-symbols": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", - "integrity": "sha1-tVOaqPwiWj0a0XlHbd8ja0QPUuQ=" + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=" }, - "path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + "astral-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", + "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", + "dev": true }, - "pause": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", - "integrity": "sha1-HUCLP9t2kjuVQ9lvtMnf1TXZy10=" + "async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=" }, - "performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + "async-each": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", + "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==" }, - "proxy-addr": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.3.tgz", - "integrity": "sha512-jQTChiCJteusULxjBp8+jftSQE5Obdl3k4cnmLA6WXtK6XFuWRnvVL7aCiBqaLPM8c4ph0S4tKna8XvmIwEnXQ==", - "requires": { - "forwarded": "~0.1.2", - "ipaddr.js": "1.6.0" - } + "async-foreach": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/async-foreach/-/async-foreach-0.1.3.tgz", + "integrity": "sha1-NhIfhFwFeBct5Bmpfb6x0W7DRUI=" }, - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + "async-limiter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", + "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==" }, - "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, - "random-bytes": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", - "integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs=" + "atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==" }, - "range-parser": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", - "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=" + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" }, - "raw-body": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz", - "integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==", + "aws4": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" + }, + "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": { - "bytes": "3.0.0", - "http-errors": "1.6.3", - "iconv-lite": "0.4.23", - "unpipe": "1.0.0" + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + }, + "dependencies": { + "regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" + } } }, - "regexp-clone": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/regexp-clone/-/regexp-clone-0.0.1.tgz", - "integrity": "sha1-p8LgmJH9vzj7sQ03b7cwA+aKxYk=" + "backo2": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", + "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=" }, - "request": { - "version": "2.87.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.87.0.tgz", - "integrity": "sha512-fcogkm7Az5bsS6Sl0sibkbhcKsnyon/jV1kF3ajGmF0c8HrttdKTPRT9hieOaQHA5HEq6r8OyWOo/o781C1tNw==", - "requires": { - "aws-sign2": "~0.7.0", - "aws4": "^1.6.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.5", - "extend": "~3.0.1", - "forever-agent": "~0.6.1", - "form-data": "~2.3.1", - "har-validator": "~5.0.3", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.17", - "oauth-sign": "~0.8.2", - "performance-now": "^2.1.0", - "qs": "~6.5.1", - "safe-buffer": "^5.1.1", - "tough-cookie": "~2.3.3", - "tunnel-agent": "^0.6.0", - "uuid": "^3.1.0" - } + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, - "require_optional": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require_optional/-/require_optional-1.0.1.tgz", - "integrity": "sha512-qhM/y57enGWHAe3v/NcwML6a3/vfESLe/sGM2dII+gEO0BpKRUkWZow/tyloNqJyN6kXSl3RyyM8Ll5D/sJP8g==", + "base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", "requires": { - "resolve-from": "^2.0.0", - "semver": "^5.1.0" + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } } }, - "resolve-from": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz", - "integrity": "sha1-lICrIOlP+h2egKgEx+oUdhGWa1c=" + "base64-arraybuffer": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz", + "integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg=" }, - "safe-buffer": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" + "base64id": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-1.0.0.tgz", + "integrity": "sha1-R2iMuZu2gE8OBtPnY7HDLlfY5rY=" }, - "safer-buffer": { - "version": "2.1.2", + "base64url": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/base64url/-/base64url-3.0.1.tgz", + "integrity": "sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==" + }, + "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" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + } + } + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "optional": true, + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "bcryptjs": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", + "integrity": "sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms=" + }, + "better-assert": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz", + "integrity": "sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI=", + "requires": { + "callsite": "1.0.0" + } + }, + "binary-extensions": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", + "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==" + }, + "bl": { + "version": "0.9.5", + "resolved": "https://registry.npmjs.org/bl/-/bl-0.9.5.tgz", + "integrity": "sha1-wGt5evCF6gC8Unr8jvzxHeIjIFQ=", + "requires": { + "readable-stream": "~1.0.26" + }, + "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.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "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=" + } + } + }, + "blob": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz", + "integrity": "sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig==" + }, + "block-stream": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", + "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=", + "requires": { + "inherits": "~2.0.0" + } + }, + "bluebird": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", + "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==" + }, + "body-parser": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", + "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", + "requires": { + "bytes": "3.1.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "on-finished": "~2.3.0", + "qs": "6.7.0", + "raw-body": "2.4.0", + "type-is": "~1.6.17" + }, + "dependencies": { + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "mime-db": { + "version": "1.40.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", + "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==" + }, + "mime-types": { + "version": "2.1.24", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", + "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", + "requires": { + "mime-db": "1.40.0" + } + }, + "qs": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + } + } + }, + "boom": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/boom/-/boom-0.4.2.tgz", + "integrity": "sha1-emNune1O/O+xnO9JR6PGffrukRs=", + "requires": { + "hoek": "0.9.x" + }, + "dependencies": { + "hoek": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-0.9.1.tgz", + "integrity": "sha1-PTIkYrrfB3Fup+uFuviAec3c5QU=" + } + } + }, + "boxen": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-1.3.0.tgz", + "integrity": "sha512-TNPjfTr432qx7yOjQyaXm3dSR0MH9vXp7eT1BFSl/C51g+EFnOR9hTg1IreahGBmDNCehscshe45f+C1TBZbLw==", + "requires": { + "ansi-align": "^2.0.0", + "camelcase": "^4.0.0", + "chalk": "^2.0.1", + "cli-boxes": "^1.0.0", + "string-width": "^2.0.0", + "term-size": "^1.2.0", + "widest-line": "^2.0.0" + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "browserslist": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.6.1.tgz", + "integrity": "sha512-1MC18ooMPRG2UuVFJTHFIAkk6mpByJfxCrnUyvSlu/hyQSFHMrlhM02SzNuCV+quTP4CKmqtOMAIjrifrpBJXQ==", + "requires": { + "caniuse-lite": "^1.0.30000971", + "electron-to-chromium": "^1.3.137", + "node-releases": "^1.1.21" + } + }, + "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-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" + }, + "buffer-writer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz", + "integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==" + }, + "busboy": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-0.2.14.tgz", + "integrity": "sha1-bCpiLvz0fFe7vh4qnDetNseSVFM=", + "requires": { + "dicer": "0.2.5", + "readable-stream": "1.1.x" + }, + "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=" + } + } + }, + "bytes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" + }, + "cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "requires": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + } + }, + "caching-transform": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-3.0.2.tgz", + "integrity": "sha512-Mtgcv3lh3U0zRii/6qVgQODdPA4G3zhG+jtbCWj39RXuUFTMzH0vcdMtaJS1jPowd+It2Pqr6y3NJMQqOqCE2w==", + "dev": true, + "requires": { + "hasha": "^3.0.0", + "make-dir": "^2.0.0", + "package-hash": "^3.0.0", + "write-file-atomic": "^2.4.2" + } + }, + "call-me-maybe": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz", + "integrity": "sha1-JtII6onje1y95gJQoV8DHBak1ms=" + }, + "callsite": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", + "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=" + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, + "camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=" + }, + "camelcase-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", + "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", + "requires": { + "camelcase": "^2.0.0", + "map-obj": "^1.0.0" + }, + "dependencies": { + "camelcase": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", + "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=" + } + } + }, + "camelify": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/camelify/-/camelify-0.0.2.tgz", + "integrity": "sha1-4UNHKrbvJbiUpOhcNUyZd6sfHhU=" + }, + "caniuse-lite": { + "version": "1.0.30000971", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000971.tgz", + "integrity": "sha512-TQFYFhRS0O5rdsmSbF1Wn+16latXYsQJat66f7S7lizXW1PVpWJeZw9wqqVLIjuxDRz7s7xRUj13QCfd8hKn6g==" + }, + "capture-stack-trace": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz", + "integrity": "sha512-mYQLZnx5Qt1JgB1WEiMCf2647plpGeQ2NMR/5L0HNZzGQo4fuSPnK+wjfPnKZV0aiJDgzmWqqkV/g7JD+DW0qw==" + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + }, + "chai": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz", + "integrity": "sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==", + "dev": true, + "requires": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^3.0.1", + "get-func-name": "^2.0.0", + "pathval": "^1.1.0", + "type-detect": "^4.0.5" + } + }, + "chai-http": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/chai-http/-/chai-http-4.3.0.tgz", + "integrity": "sha512-zFTxlN7HLMv+7+SPXZdkd5wUlK+KxH6Q7bIEMiEx0FK3zuuMqL7cwICAQ0V1+yYRozBburYuxN1qZstgHpFZQg==", + "dev": true, + "requires": { + "@types/chai": "4", + "@types/superagent": "^3.8.3", + "cookiejar": "^2.1.1", + "is-ip": "^2.0.0", + "methods": "^1.1.2", + "qs": "^6.5.1", + "superagent": "^3.7.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "chance": { + "version": "1.0.18", + "resolved": "https://registry.npmjs.org/chance/-/chance-1.0.18.tgz", + "integrity": "sha512-g9YLQVHVZS/3F+zIicfB58vjcxopvYQRp7xHzvyDFDhXH1aRZI/JhwSAO0X5qYiQluoGnaNAU6wByD2KTxJN1A==" + }, + "chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true + }, + "charenc": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", + "integrity": "sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc=" + }, + "check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", + "dev": true + }, + "cheke": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/cheke/-/cheke-1.0.5.tgz", + "integrity": "sha512-Eth7goj7dhH0isQYHUCpaQ3KrYgudxGoFMnoON6z7M/JVJZdRn6+wWnmpDtPNua/4bYTt1cXT+m745twtfLs3w==" + }, + "chokidar": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.5.tgz", + "integrity": "sha512-i0TprVWp+Kj4WRPtInjexJ8Q+BqTE909VpH8xVhXrJkoc5QC8VO9TryGOqTr+2hljzc1sC62t22h5tZePodM/A==", + "requires": { + "anymatch": "^2.0.0", + "async-each": "^1.0.1", + "braces": "^2.3.2", + "fsevents": "^1.2.7", + "glob-parent": "^3.1.0", + "inherits": "^2.0.3", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "normalize-path": "^3.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.2.1", + "upath": "^1.1.1" + } + }, + "ci-info": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.6.0.tgz", + "integrity": "sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==" + }, + "class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "requires": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "cli-boxes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-1.0.0.tgz", + "integrity": "sha1-T6kXw+WclKAEzWH47lCdplFocUM=" + }, + "cli-color": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/cli-color/-/cli-color-1.4.0.tgz", + "integrity": "sha512-xu6RvQqqrWEo6MPR1eixqGPywhYBHRs653F9jfXB2Hx4jdM/3WxiNE1vppRmxtMIfl16SFYTpYlrnqH/HsK/2w==", + "requires": { + "ansi-regex": "^2.1.1", + "d": "1", + "es5-ext": "^0.10.46", + "es6-iterator": "^2.0.3", + "memoizee": "^0.4.14", + "timers-ext": "^0.1.5" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + } + } + }, + "cli-cursor": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "dev": true, + "requires": { + "restore-cursor": "^2.0.0" + } + }, + "cli-width": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", + "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", + "dev": true + }, + "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" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "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=", + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "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" + } + } + } + }, + "clone": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.0.0.tgz", + "integrity": "sha1-32XTyhQuSkpH2zPaNGjQiKFvx24=" + }, + "cloudinary": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/cloudinary/-/cloudinary-1.14.0.tgz", + "integrity": "sha512-auJY4aD1UGnQTD2iy6kuTA+FJsqyrr9svG12WYSPAO6oRGRf+16UFDDBNuRMm37wj8Tyn8LyiED0b+Jha36ORA==", + "requires": { + "lodash": "^4.17.11", + "q": "^1.5.1" + } + }, + "cls-bluebird": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cls-bluebird/-/cls-bluebird-2.1.0.tgz", + "integrity": "sha1-N+8eCAqP+1XC9BZPU28ZGeeWiu4=", + "requires": { + "is-bluebird": "^1.0.2", + "shimmer": "^1.1.0" + } + }, + "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=" + }, + "collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "requires": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "colors": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/colors/-/colors-0.6.2.tgz", + "integrity": "sha1-JCP+ZnisDF2uiFLl0OW+CMmXq8w=" + }, + "combined-stream": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", + "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "commander": { + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz", + "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==" + }, + "commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=" + }, + "component-bind": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz", + "integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=" + }, + "component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" + }, + "component-inherit": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz", + "integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=" + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "concurrently": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-4.1.0.tgz", + "integrity": "sha512-pwzXCE7qtOB346LyO9eFWpkFJVO3JQZ/qU/feGeaAHiX1M3Rw3zgXKc5cZ8vSH5DGygkjzLFDzA/pwoQDkRNGg==", + "requires": { + "chalk": "^2.4.1", + "date-fns": "^1.23.0", + "lodash": "^4.17.10", + "read-pkg": "^4.0.1", + "rxjs": "^6.3.3", + "spawn-command": "^0.0.2-1", + "supports-color": "^4.5.0", + "tree-kill": "^1.1.0", + "yargs": "^12.0.1" + }, + "dependencies": { + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" + }, + "cliui": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", + "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", + "requires": { + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", + "wrap-ansi": "^2.0.0" + } + }, + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "requires": { + "locate-path": "^3.0.0" + } + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "requires": { + "pump": "^3.0.0" + } + }, + "has-flag": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", + "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=" + }, + "invert-kv": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", + "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==" + }, + "lcid": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", + "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", + "requires": { + "invert-kv": "^2.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "os-locale": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", + "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", + "requires": { + "execa": "^1.0.0", + "lcid": "^2.0.0", + "mem": "^4.0.0" + } + }, + "p-limit": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", + "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" + }, + "read-pkg": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-4.0.1.tgz", + "integrity": "sha1-ljYlN48+HE1IyFhytabsfV0JMjc=", + "requires": { + "normalize-package-data": "^2.3.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0" + } + }, + "supports-color": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", + "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=", + "requires": { + "has-flag": "^2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" + }, + "yargs": { + "version": "12.0.5", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", + "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", + "requires": { + "cliui": "^4.0.0", + "decamelize": "^1.2.0", + "find-up": "^3.0.0", + "get-caller-file": "^1.0.1", + "os-locale": "^3.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1 || ^4.0.0", + "yargs-parser": "^11.1.1" + } + }, + "yargs-parser": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", + "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + }, + "config-chain": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.12.tgz", + "integrity": "sha512-a1eOIcu8+7lUInge4Rpf/n4Krkf3Dd9lqhljRzII1/Zno/kRtUWnznPO3jOKBmTEktkt3fkxisUcivoj0ebzoA==", + "requires": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, + "configstore": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-3.1.2.tgz", + "integrity": "sha512-vtv5HtGjcYUgFrXc6Kx747B83MRRVS5R1VTEQoXvuP+kMI+if6uywV0nDGoiydJRy4yk7h9od5Og0kxx4zUXmw==", + "requires": { + "dot-prop": "^4.1.0", + "graceful-fs": "^4.1.2", + "make-dir": "^1.0.0", + "unique-string": "^1.0.0", + "write-file-atomic": "^2.0.0", + "xdg-basedir": "^3.0.0" + }, + "dependencies": { + "make-dir": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", + "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "requires": { + "pify": "^3.0.0" + } + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" + } + } + }, + "connect": { + "version": "3.6.6", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.6.6.tgz", + "integrity": "sha1-Ce/2xVr3I24TcTWnJXSFi2eG9SQ=", + "requires": { + "debug": "2.6.9", + "finalhandler": "1.1.0", + "parseurl": "~1.3.2", + "utils-merge": "1.0.1" + }, + "dependencies": { + "finalhandler": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.0.tgz", + "integrity": "sha1-zgtoVbRYU+eRsvzGgARtiCU91/U=", + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.1", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.2", + "statuses": "~1.3.1", + "unpipe": "~1.0.0" + } + }, + "statuses": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", + "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=" + } + } + }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" + }, + "contains-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz", + "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=", + "dev": true + }, + "content-disposition": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", + "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "requires": { + "safe-buffer": "5.1.2" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + } + } + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" + }, + "convert-source-map": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz", + "integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==", + "requires": { + "safe-buffer": "~5.1.1" + } + }, + "cookie": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", + "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + }, + "cookiejar": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.2.tgz", + "integrity": "sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA==" + }, + "copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=" + }, + "core-js": { + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.5.tgz", + "integrity": "sha512-klh/kDpwX8hryYL14M9w/xei6vrv6sE8gTHDG7/T/+SEovB/G4ejwcfE/CBzO6Edsu+OETZMZ3wcX/EjUkrl5A==" + }, + "core-js-compat": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.1.3.tgz", + "integrity": "sha512-EP018pVhgwsKHz3YoN1hTq49aRe+h017Kjz0NQz3nXV0cCRMvH3fLQl+vEPGr4r4J5sk4sU3tUC7U1aqTCeJeA==", + "requires": { + "browserslist": "^4.6.0", + "core-js-pure": "3.1.3", + "semver": "^6.1.0" + }, + "dependencies": { + "semver": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.1.1.tgz", + "integrity": "sha512-rWYq2e5iYW+fFe/oPPtYJxYgjBm8sC4rmoGdUOgBB7VnwKt6HrL793l2voH1UlsyYZpJ4g0wfjnTEO1s1NP2eQ==" + } + } + }, + "core-js-pure": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.1.3.tgz", + "integrity": "sha512-k3JWTrcQBKqjkjI0bkfXS0lbpWPxYuHWfMMjC1VDmzU4Q58IwSbuXSo99YO/hUHlw/EB4AlfA2PVxOGkrIq6dA==" + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "requires": { + "object-assign": "^4", + "vary": "^1" + } + }, + "coveralls": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/coveralls/-/coveralls-3.0.3.tgz", + "integrity": "sha512-viNfeGlda2zJr8Gj1zqXpDMRjw9uM54p7wzZdvLRyOgnAfCe974Dq4veZkjJdxQXbmdppu6flEajFYseHYaUhg==", + "dev": true, + "requires": { + "growl": "~> 1.10.0", + "js-yaml": "^3.11.0", + "lcov-parse": "^0.0.10", + "log-driver": "^1.2.7", + "minimist": "^1.2.0", + "request": "^2.86.0" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + } + } + }, + "cp-file": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/cp-file/-/cp-file-6.2.0.tgz", + "integrity": "sha512-fmvV4caBnofhPe8kOcitBwSn2f39QLjnAnGq3gO9dfd75mUytzKNZB1hde6QHunW2Rt+OwuBOMc3i1tNElbszA==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "make-dir": "^2.0.0", + "nested-error-stacks": "^2.0.0", + "pify": "^4.0.1", + "safe-buffer": "^5.0.1" + }, + "dependencies": { + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true + } + } + }, + "create-error-class": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/create-error-class/-/create-error-class-3.0.2.tgz", + "integrity": "sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y=", + "requires": { + "capture-stack-trace": "^1.0.0" + } + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "crypt": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", + "integrity": "sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs=" + }, + "cryptiles": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-0.2.2.tgz", + "integrity": "sha1-7ZH/HxetE9N0gohZT4pIoNJvMlw=", + "requires": { + "boom": "0.4.x" + } + }, + "crypto-random-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz", + "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=" + }, + "ctype": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/ctype/-/ctype-0.5.3.tgz", + "integrity": "sha1-gsGMJGH3QRTvFsE1IkrQuRRMoS8=" + }, + "currently-unhandled": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", + "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", + "requires": { + "array-find-index": "^1.0.1" + } + }, + "d": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz", + "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", + "requires": { + "es5-ext": "^0.10.9" + } + }, + "dag-map": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/dag-map/-/dag-map-1.0.2.tgz", + "integrity": "sha1-6DefBBAA7VYfxRVHXB7SyF7s6Nc=" + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "datauri": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/datauri/-/datauri-2.0.0.tgz", + "integrity": "sha512-zS2HSf9pI5XPlNZgIqJg/wCJpecgU/HA6E/uv2EfaWnW1EiTGLfy/EexTIsC9c99yoCOTXlqeeWk4FkCSuO3/g==", + "requires": { + "image-size": "^0.7.3", + "mimer": "^1.0.0" + } + }, + "date-fns": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.30.1.tgz", + "integrity": "sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==" + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" + }, + "decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=" + }, + "deep-eql": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", + "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "dev": true, + "requires": { + "type-detect": "^4.0.0" + } + }, + "deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" + }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "deepmerge": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-2.2.1.tgz", + "integrity": "sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA==" + }, + "default-require-extensions": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-2.0.0.tgz", + "integrity": "sha1-9fj7sYp9bVCyH2QfZJ67Uiz+JPc=", + "dev": true, + "requires": { + "strip-bom": "^3.0.0" + } + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "requires": { + "object-keys": "^1.0.12" + } + }, + "define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "requires": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "dependencies": { + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + }, + "dicer": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.2.5.tgz", + "integrity": "sha1-WZbAhrszIYyBLAkL3cCc0S+stw8=", + "requires": { + "readable-stream": "1.1.x", + "streamsearch": "0.1.2" + }, + "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=" + } + } + }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "dot-prop": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz", + "integrity": "sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ==", + "requires": { + "is-obj": "^1.0.0" + } + }, + "dotenv": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-6.2.0.tgz", + "integrity": "sha512-HygQCKUBSFl8wKQZBSemMywRWcEDNidvNbjGVyZu3nbZ8qq9ubiPoGLMdRDpfSrpkkm9BXYFkpKxxFX38o/76w==" + }, + "dottie": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/dottie/-/dottie-2.0.1.tgz", + "integrity": "sha512-ch5OQgvGDK2u8pSZeSYAQaV/lczImd7pMJ7BcEPXmnFVjy4yJIzP6CsODJUTH8mg1tyH1Z2abOiuJO3DjZ/GBw==" + }, + "duplexer3": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", + "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=" + }, + "ecc-jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", + "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", + "optional": true, + "requires": { + "jsbn": "~0.1.0" + } + }, + "ecdsa-sig-formatter": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.10.tgz", + "integrity": "sha1-HFlQAPBKiJffuFAAiSoPTDOvhsM=", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "editorconfig": { + "version": "0.15.3", + "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-0.15.3.tgz", + "integrity": "sha512-M9wIMFx96vq0R4F+gRpY3o2exzb8hEj/n9S8unZtHSvYjibBp/iMufSzvmOcV/laG0ZtuTVGtiJggPOSW2r93g==", + "requires": { + "commander": "^2.19.0", + "lru-cache": "^4.1.5", + "semver": "^5.6.0", + "sigmund": "^1.0.1" + }, + "dependencies": { + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==" + } + } + }, + "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.1", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.6.1.tgz", + "integrity": "sha512-0xy4A/twfrRCnkhfk8ErDi5DqdAsAqeGxht4xkCUrsvhhbQNs7E+4jV0CN7+NKIY0aHE72+XvqtBIXzD31ZbXQ==" + }, + "electron-to-chromium": { + "version": "1.3.141", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.141.tgz", + "integrity": "sha512-DdQaeP8yQNYFdivOrp37UNAZMvyZP//+SWYMVJD31A/3gbI1J6olQs8tuRaHL2ij7dubhCDXhlE4F97mrT8KGQ==" + }, + "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 + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" + }, + "end-of-stream": { + "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==", + "requires": { + "once": "^1.4.0" + } + }, + "engine.io": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.3.2.tgz", + "integrity": "sha512-AsaA9KG7cWPXWHp5FvHdDWY3AMWeZ8x+2pUVLcn71qE5AtAzgGbxuclOytygskw8XGmiQafTmnI9Bix3uihu2w==", + "requires": { + "accepts": "~1.3.4", + "base64id": "1.0.0", + "cookie": "0.3.1", + "debug": "~3.1.0", + "engine.io-parser": "~2.1.0", + "ws": "~6.1.0" + }, + "dependencies": { + "cookie": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", + "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + } + } + }, + "engine.io-client": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.3.2.tgz", + "integrity": "sha512-y0CPINnhMvPuwtqXfsGuWE8BB66+B6wTtCofQDRecMQPYX3MYUZXFNKDhdrSe3EVjgOu4V3rxdeqN/Tr91IgbQ==", + "requires": { + "component-emitter": "1.2.1", + "component-inherit": "0.0.3", + "debug": "~3.1.0", + "engine.io-parser": "~2.1.1", + "has-cors": "1.1.0", + "indexof": "0.0.1", + "parseqs": "0.0.5", + "parseuri": "0.0.5", + "ws": "~6.1.0", + "xmlhttprequest-ssl": "~1.5.4", + "yeast": "0.1.2" + }, + "dependencies": { + "component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=" + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + } + } + }, + "engine.io-parser": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.1.3.tgz", + "integrity": "sha512-6HXPre2O4Houl7c4g7Ic/XzPnHBvaEmN90vtRO9uLmwtRqQmTOw0QMevL1TOfL2Cpu1VzsaTmMotQgMdkzGkVA==", + "requires": { + "after": "0.8.2", + "arraybuffer.slice": "~0.0.7", + "base64-arraybuffer": "0.1.5", + "blob": "0.0.5", + "has-binary2": "~1.0.2" + } + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "errorhandler": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/errorhandler/-/errorhandler-1.5.1.tgz", + "integrity": "sha512-rcOwbfvP1WTViVoUjcfZicVzjhjTuhSMntHh6mW3IrEiyE6mJyXvsToJUJGlGlw/2xU9P5whlWNGlIDVeCiT4A==", + "requires": { + "accepts": "~1.3.7", + "escape-html": "~1.0.3" + } + }, + "es-abstract": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.13.0.tgz", + "integrity": "sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg==", + "requires": { + "es-to-primitive": "^1.2.0", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "is-callable": "^1.1.4", + "is-regex": "^1.0.4", + "object-keys": "^1.0.12" + } + }, + "es-to-primitive": { + "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==", + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "es5-ext": { + "version": "0.10.50", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.50.tgz", + "integrity": "sha512-KMzZTPBkeQV/JcSQhI5/z6d9VWJ3EnQ194USTUwIYZ2ZbpN8+SGXQKt1h68EX44+qt+Fzr8DO17vnxrw7c3agw==", + "requires": { + "es6-iterator": "~2.0.3", + "es6-symbol": "~3.1.1", + "next-tick": "^1.0.0" + } + }, + "es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "dev": true + }, + "es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", + "requires": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "es6-symbol": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", + "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", + "requires": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, + "es6-weak-map": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.2.tgz", + "integrity": "sha1-XjqzIlH/0VOKH45f+hNXdy+S2W8=", + "requires": { + "d": "1", + "es5-ext": "^0.10.14", + "es6-iterator": "^2.0.1", + "es6-symbol": "^3.1.1" + } + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + }, + "eslint": { + "version": "5.16.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.16.0.tgz", + "integrity": "sha512-S3Rz11i7c8AA5JPv7xAH+dOyq/Cu/VXHiHXBPOU1k/JAM5dXqQPt3qcrhpHSorXmrpu2g0gkIBVXAqCpzfoZIg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "ajv": "^6.9.1", + "chalk": "^2.1.0", + "cross-spawn": "^6.0.5", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "eslint-scope": "^4.0.3", + "eslint-utils": "^1.3.1", + "eslint-visitor-keys": "^1.0.0", + "espree": "^5.0.1", + "esquery": "^1.0.1", + "esutils": "^2.0.2", + "file-entry-cache": "^5.0.1", + "functional-red-black-tree": "^1.0.1", + "glob": "^7.1.2", + "globals": "^11.7.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "inquirer": "^6.2.2", + "js-yaml": "^3.13.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.3.0", + "lodash": "^4.17.11", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "natural-compare": "^1.4.0", + "optionator": "^0.8.2", + "path-is-inside": "^1.0.2", + "progress": "^2.0.0", + "regexpp": "^2.0.1", + "semver": "^5.5.1", + "strip-ansi": "^4.0.0", + "strip-json-comments": "^2.0.1", + "table": "^5.2.3", + "text-table": "^0.2.0" + }, + "dependencies": { + "ajv": { + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz", + "integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==", + "dev": true, + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", + "dev": true + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true + } + } + }, + "eslint-config-airbnb-base": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-13.1.0.tgz", + "integrity": "sha512-XWwQtf3U3zIoKO1BbHh6aUhJZQweOwSt4c2JrPDg9FP3Ltv3+YfEv7jIDB8275tVnO/qOHbfuYg3kzw6Je7uWw==", + "dev": true, + "requires": { + "eslint-restricted-globals": "^0.1.1", + "object.assign": "^4.1.0", + "object.entries": "^1.0.4" + } + }, + "eslint-import-resolver-node": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.2.tgz", + "integrity": "sha512-sfmTqJfPSizWu4aymbPr4Iidp5yKm8yDkHp+Ir3YiTHiiDfxh69mOUsmiqW6RZ9zRXFaF64GtYmN7e+8GHBv6Q==", + "dev": true, + "requires": { + "debug": "^2.6.9", + "resolve": "^1.5.0" + } + }, + "eslint-module-utils": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.4.0.tgz", + "integrity": "sha512-14tltLm38Eu3zS+mt0KvILC3q8jyIAH518MlG+HO0p+yK885Lb1UHTY/UgR91eOyGdmxAPb+OLoW4znqIT6Ndw==", + "dev": true, + "requires": { + "debug": "^2.6.8", + "pkg-dir": "^2.0.0" + } + }, + "eslint-plugin-import": { + "version": "2.17.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.17.3.tgz", + "integrity": "sha512-qeVf/UwXFJbeyLbxuY8RgqDyEKCkqV7YC+E5S5uOjAp4tOc8zj01JP3ucoBM8JcEqd1qRasJSg6LLlisirfy0Q==", + "dev": true, + "requires": { + "array-includes": "^3.0.3", + "contains-path": "^0.1.0", + "debug": "^2.6.9", + "doctrine": "1.5.0", + "eslint-import-resolver-node": "^0.3.2", + "eslint-module-utils": "^2.4.0", + "has": "^1.0.3", + "lodash": "^4.17.11", + "minimatch": "^3.0.4", + "read-pkg-up": "^2.0.0", + "resolve": "^1.11.0" + }, + "dependencies": { + "doctrine": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", + "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "isarray": "^1.0.0" + } + }, + "resolve": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.11.0.tgz", + "integrity": "sha512-WL2pBDjqT6pGUNSUzMw00o4T7If+z4H2x3Gz893WoUQ5KW8Vr9txp00ykiP16VBaZF5+j/OcXJHZ9+PCvdiDKw==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + } + } + }, + "eslint-restricted-globals": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/eslint-restricted-globals/-/eslint-restricted-globals-0.1.1.tgz", + "integrity": "sha1-NfDVy8ZMLj7WLpO0saevBbp+1Nc=", + "dev": true + }, + "eslint-scope": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", + "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", + "dev": true, + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + }, + "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 + }, + "eslint-visitor-keys": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", + "integrity": "sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==", + "dev": true + }, + "espree": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-5.0.1.tgz", + "integrity": "sha512-qWAZcWh4XE/RwzLJejfcofscgMc9CamR6Tn1+XRXNzrvUSSbiAjGOI/fggztjIi7y9VLPqnICMIPiGyr8JaZ0A==", + "dev": true, + "requires": { + "acorn": "^6.0.7", + "acorn-jsx": "^5.0.0", + "eslint-visitor-keys": "^1.0.0" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" + }, + "esquery": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", + "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", + "dev": true, + "requires": { + "estraverse": "^4.0.0" + } + }, + "esrecurse": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", + "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", + "dev": true, + "requires": { + "estraverse": "^4.1.0" + } + }, + "estraverse": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", + "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", + "dev": true + }, + "esutils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=" + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" + }, + "event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", + "requires": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, + "execa": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", + "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", + "requires": { + "cross-spawn": "^5.0.1", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, + "dependencies": { + "cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "requires": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + } + } + }, + "exit-hook": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz", + "integrity": "sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g=" + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "express": { + "version": "4.17.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", + "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", + "requires": { + "accepts": "~1.3.7", + "array-flatten": "1.1.1", + "body-parser": "1.19.0", + "content-disposition": "0.5.3", + "content-type": "~1.0.4", + "cookie": "0.4.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.1.2", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.5", + "qs": "6.7.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.1.2", + "send": "0.17.1", + "serve-static": "1.14.1", + "setprototypeof": "1.1.1", + "statuses": "~1.5.0", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "dependencies": { + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + }, + "mime-db": { + "version": "1.40.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", + "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==" + }, + "mime-types": { + "version": "2.1.24", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", + "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", + "requires": { + "mime-db": "1.40.0" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" + }, + "qs": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" + }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "send": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", + "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", + "requires": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.7.2", + "mime": "1.6.0", + "ms": "2.1.1", + "on-finished": "~2.3.0", + "range-parser": "~1.2.1", + "statuses": "~1.5.0" + } + }, + "serve-static": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", + "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.17.1" + } + }, + "setprototypeof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + } + } + }, + "express-jwt": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/express-jwt/-/express-jwt-5.3.1.tgz", + "integrity": "sha512-1C9RNq0wMp/JvsH/qZMlg3SIPvKu14YkZ4YYv7gJQ1Vq+Dv8LH9tLKenS5vMNth45gTlEUGx+ycp9IHIlaHP/g==", + "requires": { + "async": "^1.5.0", + "express-unless": "^0.3.0", + "jsonwebtoken": "^8.1.0", + "lodash.set": "^4.0.0" + } + }, + "express-session": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.16.1.tgz", + "integrity": "sha512-pWvUL8Tl5jUy1MLH7DhgUlpoKeVPUTe+y6WQD9YhcN0C5qAhsh4a8feVjiUXo3TFhIy191YGZ4tewW9edbl2xQ==", + "requires": { + "cookie": "0.3.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-headers": "~1.0.2", + "parseurl": "~1.3.2", + "safe-buffer": "5.1.2", + "uid-safe": "~2.1.5" + }, + "dependencies": { + "cookie": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", + "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" + }, + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + }, + "on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==" + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + } + } + }, + "express-unless": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/express-unless/-/express-unless-0.3.1.tgz", + "integrity": "sha1-JVfBRudb65A+LSR/m1ugFFJpbiA=" + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "external-editor": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.0.3.tgz", + "integrity": "sha512-bn71H9+qWoOQKyZDo25mOMVpSmXROAsTJVVVYzrrtol3d4y+AsKjf4Iwl2Q+IuT0kFSQ1qo166UuIwqYq7mGnA==", + "dev": true, + "requires": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + }, + "dependencies": { + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + } + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "file-entry-cache": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", + "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", + "dev": true, + "requires": { + "flat-cache": "^2.0.1" + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + }, + "dependencies": { + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" + } + } + }, + "find-cache-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", + "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", + "requires": { + "commondir": "^1.0.1", + "make-dir": "^2.0.0", + "pkg-dir": "^3.0.0" + }, + "dependencies": { + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "requires": { + "locate-path": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", + "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" + }, + "pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "requires": { + "find-up": "^3.0.0" + } + } + } + }, + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "findup": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/findup/-/findup-0.1.5.tgz", + "integrity": "sha1-itkpozk7rGJ5V6fl3kYjsGsOLOs=", + "requires": { + "colors": "~0.6.0-1", + "commander": "~2.1.0" + }, + "dependencies": { + "commander": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.1.0.tgz", + "integrity": "sha1-0SG7roYNmZKj1Re6lvVliOR8Z4E=" + } + } + }, + "flat": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.0.tgz", + "integrity": "sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw==", + "dev": true, + "requires": { + "is-buffer": "~2.0.3" + }, + "dependencies": { + "is-buffer": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.3.tgz", + "integrity": "sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw==", + "dev": true + } + } + }, + "flat-cache": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", + "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", + "dev": true, + "requires": { + "flatted": "^2.0.0", + "rimraf": "2.6.3", + "write": "1.0.3" + } + }, + "flatted": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.0.tgz", + "integrity": "sha512-R+H8IZclI8AAkSBRQJLVOsxwAoHd6WC40b4QTNWIjzAa6BXOBfQcM587MXDTVPeYaopFNWHUFLx7eNmHDSxMWg==", + "dev": true + }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=" + }, + "foreground-child": { + "version": "1.5.6", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-1.5.6.tgz", + "integrity": "sha1-T9ca0t/elnibmApcCilZN8svXOk=", + "dev": true, + "requires": { + "cross-spawn": "^4", + "signal-exit": "^3.0.0" + }, + "dependencies": { + "cross-spawn": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-4.0.2.tgz", + "integrity": "sha1-e5JHYhwjrf3ThWAEqCPL45dCTUE=", + "dev": true, + "requires": { + "lru-cache": "^4.0.1", + "which": "^1.2.9" + } + } + } + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + }, + "form-data": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz", + "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "1.0.6", + "mime-types": "^2.1.12" + } + }, + "format-util": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/format-util/-/format-util-1.0.3.tgz", + "integrity": "sha1-Ay3KShFiYqEsQ/TD7IVmQWxbLZU=" + }, + "formidable": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.1.tgz", + "integrity": "sha512-Fs9VRguL0gqGHkXS5GQiMCr1VhZBxz0JnJs4JmMp/2jL18Fmbzvv7vOFRU+U8TBkHEE/CX1qDXzJplVULgsLeg==" + }, + "forwarded": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", + "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" + }, + "fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "requires": { + "map-cache": "^0.2.2" + } + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" + }, + "fs-extra": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.24.0.tgz", + "integrity": "sha1-1OQ0KpZnXLeEZjOmCZJJMytTmVI=", + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^2.1.0", + "path-is-absolute": "^1.0.0", + "rimraf": "^2.2.8" + } + }, + "fs-readdir-recursive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz", + "integrity": "sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==" + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "fsevents": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.8.tgz", + "integrity": "sha512-tPvHgPGB7m40CZ68xqFGkKuzN+RnpGmSV+hgeKxhRpbxdqKXUFJGC3yonBOLzQBcJyGpdZFDfCsdOC2KFsXzeA==", + "optional": true, + "requires": { + "nan": "^2.12.1", + "node-pre-gyp": "^0.12.0" + }, + "dependencies": { + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "optional": true + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "optional": true + }, + "aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", + "optional": true + }, + "are-we-there-yet": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", + "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", + "optional": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "optional": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "optional": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "chownr": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.1.tgz", + "integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g==", + "optional": true + }, + "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=", + "optional": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "optional": true + }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", + "optional": true + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "optional": true + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "optional": true, + "requires": { + "ms": "^2.1.1" + } + }, + "deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "optional": true + }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", + "optional": true + }, + "detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", + "optional": true + }, + "fs-minipass": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.5.tgz", + "integrity": "sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ==", + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "optional": true + }, + "gauge": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "optional": true, + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "optional": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", + "optional": true + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "optional": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ignore-walk": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.1.tgz", + "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==", + "optional": true, + "requires": { + "minimatch": "^3.0.4" + } + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "optional": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "optional": true + }, + "ini": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", + "optional": true + }, + "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=", + "optional": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "optional": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "optional": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "optional": true + }, + "minipass": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.5.tgz", + "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==", + "optional": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + }, + "minizlib": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.2.1.tgz", + "integrity": "sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA==", + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "optional": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "optional": true + }, + "needle": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/needle/-/needle-2.3.0.tgz", + "integrity": "sha512-QBZu7aAFR0522EyaXZM0FZ9GLpq6lvQ3uq8gteiDUp7wKdy0lSd2hPlgFwVuW1CBkfEs9PfDQsQzZghLs/psdg==", + "optional": true, + "requires": { + "debug": "^4.1.0", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + } + }, + "node-pre-gyp": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.12.0.tgz", + "integrity": "sha512-4KghwV8vH5k+g2ylT+sLTjy5wmUOb9vPhnM8NHvRf9dHmnW/CndrFXy2aRPaPST6dugXSdHXfeaHQm77PIz/1A==", + "optional": true, + "requires": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.1", + "needle": "^2.2.1", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.2.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4" + } + }, + "nopt": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", + "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", + "optional": true, + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, + "npm-bundled": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.0.6.tgz", + "integrity": "sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g==", + "optional": true + }, + "npm-packlist": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.1.tgz", + "integrity": "sha512-+TcdO7HJJ8peiiYhvPxsEDhF3PJFGUGRcFsGve3vxvxdcpO2Z4Z7rkosRM0kWj6LfbK/P0gu3dzk5RU1ffvFcw==", + "optional": true, + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1" + } + }, + "npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "optional": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "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=", + "optional": true + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "optional": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "optional": true, + "requires": { + "wrappy": "1" + } + }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "optional": true + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "optional": true + }, + "osenv": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "optional": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "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=", + "optional": true + }, + "process-nextick-args": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", + "optional": true + }, + "rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "optional": true, + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "optional": true + } + } + }, + "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==", + "optional": true, + "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" + } + }, + "rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "optional": true, + "requires": { + "glob": "^7.1.3" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "optional": true + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "optional": true + }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "optional": true + }, + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "optional": true + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "optional": true + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "optional": true + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "optional": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.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==", + "optional": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "optional": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "optional": true + }, + "tar": { + "version": "4.4.8", + "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.8.tgz", + "integrity": "sha512-LzHF64s5chPQQS0IYBn9IN5h3i98c12bo4NCO7e0sGM2llXQ3p2FGC5sdENN4cTW48O915Sh+x+EXx7XW96xYQ==", + "optional": true, + "requires": { + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.3.4", + "minizlib": "^1.1.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.2" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "optional": true + }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "optional": true, + "requires": { + "string-width": "^1.0.2 || 2" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "optional": true + }, + "yallist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", + "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", + "optional": true + } + } + }, + "fstream": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz", + "integrity": "sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=", + "requires": { + "graceful-fs": "^4.1.2", + "inherits": "~2.0.0", + "mkdirp": ">=0.5 0", + "rimraf": "2" + } + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, + "gapitoken": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/gapitoken/-/gapitoken-0.1.5.tgz", + "integrity": "sha1-NXf8+1Qmvjp7jrrakmcSKdjMgc4=", + "requires": { + "jws": "~3.0.0", + "request": "^2.54.0" + }, + "dependencies": { + "base64url": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/base64url/-/base64url-1.0.6.tgz", + "integrity": "sha1-1k03XWinxkDZEuI1jRcNylu1RoE=", + "requires": { + "concat-stream": "~1.4.7", + "meow": "~2.0.0" + } + }, + "camelcase": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", + "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=" + }, + "camelcase-keys": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-1.0.0.tgz", + "integrity": "sha1-vRoRv5sxoc5JNJOpMN4aC69K1+w=", + "requires": { + "camelcase": "^1.0.1", + "map-obj": "^1.0.0" + } + }, + "concat-stream": { + "version": "1.4.11", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.4.11.tgz", + "integrity": "sha512-X3JMh8+4je3U1cQpG87+f9lXHDrqcb2MVLg9L7o8b1UZ0DzhRrUpdn65ttzu10PpJPPI3MQNkis+oha6TSA9Mw==", + "requires": { + "inherits": "~2.0.1", + "readable-stream": "~1.1.9", + "typedarray": "~0.0.5" + } + }, + "indent-string": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-1.2.2.tgz", + "integrity": "sha1-25m8xYPrarux5I3LsZmamGBBy2s=", + "requires": { + "get-stdin": "^4.0.1", + "minimist": "^1.1.0", + "repeating": "^1.1.0" + } + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "jwa": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.0.2.tgz", + "integrity": "sha1-/Xlgnx53Limdzo3bdtAGWd2DUR8=", + "requires": { + "base64url": "~0.0.4", + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "^1.0.0" + }, + "dependencies": { + "base64url": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/base64url/-/base64url-0.0.6.tgz", + "integrity": "sha1-lZezazMNscQkdzIuqH6oAnSZuCs=" + } + } + }, + "jws": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.0.0.tgz", + "integrity": "sha1-2l8meJfdTpz4E3l52zP8VKPAVBg=", + "requires": { + "base64url": "~1.0.4", + "jwa": "~1.0.0" + } + }, + "meow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-2.0.0.tgz", + "integrity": "sha1-j1MKjs9dQNP0tN+Tw0cpAPuiqPE=", + "requires": { + "camelcase-keys": "^1.0.0", + "indent-string": "^1.1.0", + "minimist": "^1.1.0", + "object-assign": "^1.0.0" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + }, + "object-assign": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-1.0.0.tgz", + "integrity": "sha1-5l3Idm07R7S4MHRlyDEdoDCwcKY=" + }, + "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" + } + }, + "repeating": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/repeating/-/repeating-1.1.3.tgz", + "integrity": "sha1-PUEUIYh3U3SU+X93+Xhfq4EPpKw=", + "requires": { + "is-finite": "^1.0.0" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + } + } + }, + "gauge": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "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=", + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "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" + } + } + } + }, + "gaze": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.3.tgz", + "integrity": "sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g==", + "requires": { + "globule": "^1.0.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==" + }, + "get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", + "dev": true + }, + "get-stdin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", + "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=" + }, + "get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=" + }, + "get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=" + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "requires": { + "is-extglob": "^2.1.0" + } + } + } + }, + "global-dirs": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.1.tgz", + "integrity": "sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU=", + "requires": { + "ini": "^1.3.4" + } + }, + "globals": { + "version": "11.11.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.11.0.tgz", + "integrity": "sha512-WHq43gS+6ufNOEqlrDBxVEbb8ntfXrfAUU2ZOpCxrBdGKW3gyv8mCxAfIBD0DroPKGrJ2eSsXsLtY9MPntsyTw==" + }, + "globule": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/globule/-/globule-1.2.1.tgz", + "integrity": "sha512-g7QtgWF4uYSL5/dn71WxubOrS7JVGCnFPEnoeChJmBnyR9Mw8nGoEwOgJL/RC2Te0WhbsEUCejfH8SZNJ+adYQ==", + "requires": { + "glob": "~7.1.1", + "lodash": "~4.17.10", + "minimatch": "~3.0.2" + } + }, + "googleapis": { + "version": "1.0.23", + "resolved": "https://registry.npmjs.org/googleapis/-/googleapis-1.0.23.tgz", + "integrity": "sha1-5/9JvPRhJADuyYQeZyuwnqFtgaI=", + "requires": { + "async": "~0.9.0", + "gapitoken": "~0.1.2", + "request": "~2.51.0", + "string-template": "~0.2.0" + }, + "dependencies": { + "asn1": { + "version": "0.1.11", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.1.11.tgz", + "integrity": "sha1-VZvhg3bQik7E2+gId9J4GGObLfc=" + }, + "assert-plus": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.1.5.tgz", + "integrity": "sha1-7nQAlBMALYTOxyGcasgRgS5yMWA=" + }, + "async": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz", + "integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=" + }, + "aws-sign2": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.5.0.tgz", + "integrity": "sha1-xXED96F/wDfwLXwuZLYC6iI/fWM=" + }, + "caseless": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.8.0.tgz", + "integrity": "sha1-W8oogdQUN/VLJAfr40iIx7mtT30=" + }, + "combined-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-0.0.7.tgz", + "integrity": "sha1-ATfmV7qlp1QcV6w3rF/AfXO03B8=", + "requires": { + "delayed-stream": "0.0.5" + } + }, + "delayed-stream": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-0.0.5.tgz", + "integrity": "sha1-1LH0OpPoKW3+AmlPRoC8N6MTxz8=" + }, + "forever-agent": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.5.2.tgz", + "integrity": "sha1-bQ4JxJIflKJ/Y9O0nF/v8epMUTA=" + }, + "form-data": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-0.2.0.tgz", + "integrity": "sha1-Jvi8JtpkQOKZy9z7aQNcT3em5GY=", + "requires": { + "async": "~0.9.0", + "combined-stream": "~0.0.4", + "mime-types": "~2.0.3" + }, + "dependencies": { + "mime-types": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.0.14.tgz", + "integrity": "sha1-MQ4VnbI+B3+Lsit0jav6SVcUCqY=", + "requires": { + "mime-db": "~1.12.0" + } + } + } + }, + "http-signature": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-0.10.1.tgz", + "integrity": "sha1-T72sEyVZqoMjEh5UB3nAoBKyfmY=", + "requires": { + "asn1": "0.1.11", + "assert-plus": "^0.1.5", + "ctype": "0.5.3" + } + }, + "mime-db": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.12.0.tgz", + "integrity": "sha1-PQxjGA9FjrENMlqqN9fFiuMS6dc=" + }, + "mime-types": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-1.0.2.tgz", + "integrity": "sha1-mVrhOSq4r/y/yyZB3QVOlDwNXc4=" + }, + "node-uuid": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz", + "integrity": "sha1-sEDrCSOWivq/jTL7HxfxFn/auQc=" + }, + "oauth-sign": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.5.0.tgz", + "integrity": "sha1-12f1FpMlYg6rLgh+8MRy53PbZGE=" + }, + "qs": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-2.3.3.tgz", + "integrity": "sha1-6eha2+ddoLvkyOBHaghikPhjtAQ=" + }, + "request": { + "version": "2.51.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.51.0.tgz", + "integrity": "sha1-NdALvswBLlX5B7G9ng29V3v+8m4=", + "requires": { + "aws-sign2": "~0.5.0", + "bl": "~0.9.0", + "caseless": "~0.8.0", + "combined-stream": "~0.0.5", + "forever-agent": "~0.5.0", + "form-data": "~0.2.0", + "hawk": "1.1.1", + "http-signature": "~0.10.0", + "json-stringify-safe": "~5.0.0", + "mime-types": "~1.0.1", + "node-uuid": "~1.4.0", + "oauth-sign": "~0.5.0", + "qs": "~2.3.1", + "stringstream": "~0.0.4", + "tough-cookie": ">=0.12.0", + "tunnel-agent": "~0.4.0" + } + }, + "tunnel-agent": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz", + "integrity": "sha1-Y3PbdpCf5XDgjXNYM2Xtgop07us=" + } + } + }, + "got": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/got/-/got-6.7.1.tgz", + "integrity": "sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=", + "requires": { + "create-error-class": "^3.0.0", + "duplexer3": "^0.1.4", + "get-stream": "^3.0.0", + "is-redirect": "^1.0.0", + "is-retry-allowed": "^1.0.0", + "is-stream": "^1.0.0", + "lowercase-keys": "^1.0.0", + "safe-buffer": "^5.0.1", + "timed-out": "^4.0.0", + "unzip-response": "^2.0.1", + "url-parse-lax": "^1.0.0" + } + }, + "graceful-fs": { + "version": "4.1.15", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", + "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==" + }, + "graphlib": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/graphlib/-/graphlib-2.1.7.tgz", + "integrity": "sha512-TyI9jIy2J4j0qgPmOOrHTCtpPqJGN/aurBwc6ZT+bRii+di1I+Wv3obRhVrmBEXet+qkMaEX67dXrwsd3QQM6w==", + "requires": { + "lodash": "^4.17.5" + } + }, + "green-jwt": { + "version": "0.0.8-alpha", + "resolved": "https://registry.npmjs.org/green-jwt/-/green-jwt-0.0.8-alpha.tgz", + "integrity": "sha1-sk2ZIvivYcSCPT41faJWMvqACaM=" + }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true + }, + "handlebars": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.1.2.tgz", + "integrity": "sha512-nvfrjqvt9xQ8Z/w0ijewdD/vvWDTOweBUm96NTr66Wfvo1mJenBLwcYmPs3TIBP5ruzYGD7Hx/DaM9RmhroGPw==", + "requires": { + "neo-async": "^2.6.0", + "optimist": "^0.6.1", + "source-map": "^0.6.1", + "uglify-js": "^3.1.4" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" + }, + "har-validator": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "requires": { + "ajv": "^6.5.5", + "har-schema": "^2.0.0" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "requires": { + "ansi-regex": "^2.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=" + } + } + }, + "has-binary2": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.3.tgz", + "integrity": "sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==", + "requires": { + "isarray": "2.0.1" + }, + "dependencies": { + "isarray": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", + "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=" + } + } + }, + "has-cors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz", + "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=" + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + }, + "has-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", + "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=" + }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" + }, + "has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "requires": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + } + }, + "has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "requires": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "dependencies": { + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "hasha": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hasha/-/hasha-3.0.0.tgz", + "integrity": "sha1-UqMvq4Vp1BymmmH/GiFPjrfIvTk=", + "dev": true, + "requires": { + "is-stream": "^1.0.1" + } + }, + "hawk": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/hawk/-/hawk-1.1.1.tgz", + "integrity": "sha1-h81JH5tG5OKurKM1QWdmiF0tHtk=", + "requires": { + "boom": "0.4.x", + "cryptiles": "0.2.x", + "hoek": "0.9.x", + "sntp": "0.2.x" + }, + "dependencies": { + "hoek": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-0.9.1.tgz", + "integrity": "sha1-PTIkYrrfB3Fup+uFuviAec3c5QU=" + } + } + }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true + }, + "hoek": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-6.1.3.tgz", + "integrity": "sha512-YXXAAhmF9zpQbC7LEcREFtXfGq5K1fmd+4PHkBq8NUqmzW3G+Dq10bI/i0KucLRwss3YYFQ0fSfoxBZYiGUqtQ==" + }, + "homedir-polyfill": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", + "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", + "requires": { + "parse-passwd": "^1.0.0" + } + }, + "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==" + }, + "http-errors": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", + "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + }, + "dependencies": { + "setprototypeof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + } + } + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + }, + "ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk=" + }, + "image-size": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.7.4.tgz", + "integrity": "sha512-GqPgxs+VkOr12aWwjSkyRzf5atzObWpFtiRuDgxCl2I/SDpZOKZFRD3iIAeAN6/usmn8SeLWRt7a8JRYK0Whbw==" + }, + "import-fresh": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.0.0.tgz", + "integrity": "sha512-pOnA9tfM3Uwics+SaBLCNyZZZbK+4PTu0OPZtLlMIrv17EdBoC15S9Kn8ckJ9TZTyKb3ywNE5y1yeDxxGA7nTQ==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "dependencies": { + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + } + } + }, + "import-lazy": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", + "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=" + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=" + }, + "in-publish": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/in-publish/-/in-publish-2.0.0.tgz", + "integrity": "sha1-4g/146KvwmkDILbcVSaCqcf631E=" + }, + "indent-string": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", + "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", + "requires": { + "repeating": "^2.0.0" + } + }, + "indexof": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", + "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=" + }, + "inflection": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/inflection/-/inflection-1.12.0.tgz", + "integrity": "sha1-ogCTVlbW9fa8TcdQLhrstwMihBY=" + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "ini": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" + }, + "inquirer": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.3.1.tgz", + "integrity": "sha512-MmL624rfkFt4TG9y/Jvmt8vdmOo836U7Y0Hxr2aFk3RelZEGX4Igk0KabWrcaaZaTv9uzglOqWh1Vly+FAWAXA==", + "dev": true, + "requires": { + "ansi-escapes": "^3.2.0", + "chalk": "^2.4.2", + "cli-cursor": "^2.1.0", + "cli-width": "^2.0.0", + "external-editor": "^3.0.3", + "figures": "^2.0.0", + "lodash": "^4.17.11", + "mute-stream": "0.0.7", + "run-async": "^2.2.0", + "rxjs": "^6.4.0", + "string-width": "^2.1.0", + "strip-ansi": "^5.1.0", + "through": "^2.3.6" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", + "dev": true + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "requires": { + "loose-envify": "^1.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=" + }, + "ip-regex": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", + "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=", + "dev": true + }, + "ipaddr.js": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.0.tgz", + "integrity": "sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA==" + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "requires": { + "kind-of": "^3.0.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" + } + } + } + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" + }, + "is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "requires": { + "binary-extensions": "^1.0.0" + } + }, + "is-bluebird": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-bluebird/-/is-bluebird-1.0.2.tgz", + "integrity": "sha1-CWQ5Bg9KpBGr7hkUOoTWpVNG1uI=" + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "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==" + }, + "is-ci": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.2.1.tgz", + "integrity": "sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg==", + "requires": { + "ci-info": "^1.5.0" + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "requires": { + "kind-of": "^3.0.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" + } + } + } + }, + "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=" + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" + } + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=" + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" + }, + "is-finite": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", + "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-installed-globally": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.1.0.tgz", + "integrity": "sha1-Df2Y9akRFxbdU13aZJL2e/PSWoA=", + "requires": { + "global-dirs": "^0.1.0", + "is-path-inside": "^1.0.0" + } + }, + "is-invalid-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-invalid-path/-/is-invalid-path-0.1.0.tgz", + "integrity": "sha1-MHqFWzzxqTi0TqcNLGEQYFNxTzQ=", + "requires": { + "is-glob": "^2.0.0" + }, + "dependencies": { + "is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=" + }, + "is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", + "requires": { + "is-extglob": "^1.0.0" + } + } + } + }, + "is-ip": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-ip/-/is-ip-2.0.0.tgz", + "integrity": "sha1-aO6gfooKCpTC0IDdZ0xzGrKkYas=", + "dev": true, + "requires": { + "ip-regex": "^2.0.0" + } + }, + "is-npm": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-1.0.0.tgz", + "integrity": "sha1-8vtjpl5JBbQGyGBydloaTceTufQ=" + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "requires": { + "kind-of": "^3.0.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" + } + } + } + }, + "is-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=" + }, + "is-path-inside": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", + "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", + "requires": { + "path-is-inside": "^1.0.1" + } + }, + "is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=" + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "requires": { + "isobject": "^3.0.1" + } + }, + "is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", + "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=" + }, + "is-redirect": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz", + "integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=" + }, + "is-regex": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", + "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", + "requires": { + "has": "^1.0.1" + } + }, + "is-retry-allowed": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz", + "integrity": "sha1-EaBgVotnM5REAz0BJaYaINVk+zQ=" + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "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==", + "requires": { + "has-symbols": "^1.0.0" + } + }, + "is-typedarray": { + "version": "1.0.0", + "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-valid-path": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-valid-path/-/is-valid-path-0.1.1.tgz", + "integrity": "sha1-EQ+f90w39mPh7HkV60UfLbk6yd8=", + "requires": { + "is-invalid-path": "^0.1.0" + } + }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==" + }, + "is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=" + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "isemail": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/isemail/-/isemail-3.2.0.tgz", + "integrity": "sha512-zKqkK+O+dGqevc93KNsbZ/TqTUFd46MwWjYOoMrjIMZ51eU7DtQG3Wmd9SQQT7i7RVnuTPEiYEWHU3MSbxC1Tg==", + "requires": { + "punycode": "2.x.x" + }, + "dependencies": { + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + } + } + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, + "istanbul-lib-coverage": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz", + "integrity": "sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==", + "dev": true + }, + "istanbul-lib-hook": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-2.0.7.tgz", + "integrity": "sha512-vrRztU9VRRFDyC+aklfLoeXyNdTfga2EI3udDGn4cZ6fpSXpHLV9X6CHvfoMCPtggg8zvDDmC4b9xfu0z6/llA==", + "dev": true, + "requires": { + "append-transform": "^1.0.0" + } + }, + "istanbul-lib-instrument": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-3.3.0.tgz", + "integrity": "sha512-5nnIN4vo5xQZHdXno/YDXJ0G+I3dAm4XgzfSVTPLQpj/zAV2dV6Juy0yaf10/zrJOJeHoN3fraFe+XRq2bFVZA==", + "dev": true, + "requires": { + "@babel/generator": "^7.4.0", + "@babel/parser": "^7.4.3", + "@babel/template": "^7.4.0", + "@babel/traverse": "^7.4.3", + "@babel/types": "^7.4.0", + "istanbul-lib-coverage": "^2.0.5", + "semver": "^6.0.0" + }, + "dependencies": { + "semver": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.0.0.tgz", + "integrity": "sha512-0UewU+9rFapKFnlbirLi3byoOuhrSsli/z/ihNnvM24vgF+8sNBiI1LZPBSH9wJKUwaUbw+s3hToDLCXkrghrQ==", + "dev": true + } + } + }, + "istanbul-lib-report": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-2.0.8.tgz", + "integrity": "sha512-fHBeG573EIihhAblwgxrSenp0Dby6tJMFR/HvlerBsrCTD5bkUuoNtn3gVh29ZCS824cGGBPn7Sg7cNk+2xUsQ==", + "dev": true, + "requires": { + "istanbul-lib-coverage": "^2.0.5", + "make-dir": "^2.1.0", + "supports-color": "^6.1.0" + }, + "dependencies": { + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "istanbul-lib-source-maps": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.6.tgz", + "integrity": "sha512-R47KzMtDJH6X4/YW9XTx+jrLnZnscW4VpNN+1PViSYTejLVPWv7oov+Duf8YQSPyVRUvueQqz1TcsC6mooZTXw==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^2.0.5", + "make-dir": "^2.1.0", + "rimraf": "^2.6.3", + "source-map": "^0.6.1" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, + "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 + } + } + }, + "istanbul-reports": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-2.2.6.tgz", + "integrity": "sha512-SKi4rnMyLBKe0Jy2uUdx28h8oG7ph2PPuQPvIAh31d+Ci+lSiEu4C+h3oBPuJ9+mPKhOyW0M8gY4U5NM1WLeXA==", + "dev": true, + "requires": { + "handlebars": "^4.1.2" + } + }, + "jade": { + "version": "0.26.3", + "resolved": "https://registry.npmjs.org/jade/-/jade-0.26.3.tgz", + "integrity": "sha1-jxDXl32NefL2/4YqgbBRPMslaGw=", + "requires": { + "commander": "0.6.1", + "mkdirp": "0.3.0" + }, + "dependencies": { + "commander": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-0.6.1.tgz", + "integrity": "sha1-+mihT2qUXVTbvlDYzbMyDp47GgY=" + }, + "mkdirp": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz", + "integrity": "sha1-G79asbqCevI1dRQ0kEJkVfSB/h4=" + } + } + }, + "joi": { + "version": "14.3.1", + "resolved": "https://registry.npmjs.org/joi/-/joi-14.3.1.tgz", + "integrity": "sha512-LQDdM+pkOrpAn4Lp+neNIFV3axv1Vna3j38bisbQhETPMANYRbFJFUyOZcOClYvM/hppMhGWuKSFEK9vjrB+bQ==", + "requires": { + "hoek": "6.x.x", + "isemail": "3.x.x", + "topo": "3.x.x" + } + }, + "js-base64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.5.1.tgz", + "integrity": "sha512-M7kLczedRMYX4L8Mdh4MzyAMM9O5osx+4FcOQuTvr3A9F2D9S5JXheN0ewNbrvK2UatkTRhL5ejGmGSjNMiZuw==" + }, + "js-beautify": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.10.0.tgz", + "integrity": "sha512-OMwf/tPDpE/BLlYKqZOhqWsd3/z2N3KOlyn1wsCRGFwViE8LOQTcDtathQvHvZc+q+zWmcNAbwKSC+iJoMaH2Q==", + "requires": { + "config-chain": "^1.1.12", + "editorconfig": "^0.15.3", + "glob": "^7.1.3", + "mkdirp": "~0.5.1", + "nopt": "~4.0.1" + }, + "dependencies": { + "nopt": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", + "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + } + } + }, + "js-levenshtein": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz", + "integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==" + }, + "js-string-escape": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/js-string-escape/-/js-string-escape-1.0.1.tgz", + "integrity": "sha1-4mJbrbwNZ8dTPp7cEGjFh65BN+8=" + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "optional": true + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" + }, + "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", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" + }, + "json-refs": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/json-refs/-/json-refs-2.1.7.tgz", + "integrity": "sha1-uesB/in16j6Sh48VrqEK04taz4k=", + "requires": { + "commander": "^2.9.0", + "graphlib": "^2.1.1", + "js-yaml": "^3.8.3", + "native-promise-only": "^0.8.1", + "path-loader": "^1.0.2", + "slash": "^1.0.0", + "uri-js": "^3.0.2" + }, + "dependencies": { + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + }, + "slash": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", + "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=" + }, + "uri-js": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-3.0.2.tgz", + "integrity": "sha1-+QuFhQf4HepNz7s8TD2/orVX+qo=", + "requires": { + "punycode": "^2.1.0" + } + } + } + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + }, + "json-schema-deref-sync": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/json-schema-deref-sync/-/json-schema-deref-sync-0.3.4.tgz", + "integrity": "sha512-4Ssj+1UGDJAzPIdTL1QW/rvHwWeuwC28gjbA0EjStLxVsalc+UPciKXxs3rhtr4gaGdIBojW/VmvC8B8bCQwcA==", + "requires": { + "clone": "~2.0.0", + "dag-map": "~1.0.0", + "is-valid-path": "^0.1.1", + "lodash": "^4.7.0", + "md5": "~2.2.0", + "memory-cache": "~0.1.5", + "mpath": "~0.2.1", + "traverse": "~0.6.6", + "valid-url": "~1.0.9" + }, + "dependencies": { + "mpath": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.2.1.tgz", + "integrity": "sha1-Ok6Ck1mAHeljCcJ6ay4QLon56W4=" + } + } + }, + "json-schema-ref-parser": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/json-schema-ref-parser/-/json-schema-ref-parser-5.1.3.tgz", + "integrity": "sha512-CpDFlBwz/6la78hZxyB9FECVKGYjIIl3Ms3KLqFj99W7IIb7D00/RDgc++IGB4BBALl0QRhh5m4q5WNSopvLtQ==", + "requires": { + "call-me-maybe": "^1.0.1", + "debug": "^3.1.0", + "js-yaml": "^3.12.0", + "ono": "^4.0.6" + }, + "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.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + } + } + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, + "json5": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.0.tgz", + "integrity": "sha512-8Mh9h6xViijj36g7Dxi+Y4S6hNGV96vcJZr/SrlHh1LR/pEn/8j/+qIBbs44YKl69Lrfctp4QD+AdWLTMqEZAQ==", + "requires": { + "minimist": "^1.2.0" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + } + } + }, + "jsonfile": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", + "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "jsonschema": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/jsonschema/-/jsonschema-1.2.4.tgz", + "integrity": "sha512-lz1nOH69GbsVHeVgEdvyavc/33oymY1AZwtePMiMj4HZPMbP5OIKK3zT9INMWjwua/V4Z4yq7wSlBbSG+g4AEw==" + }, + "jsonschema-draft4": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/jsonschema-draft4/-/jsonschema-draft4-1.0.0.tgz", + "integrity": "sha1-8K8gBQVPDwrefqIRhhS2ncUS2GU=" + }, + "jsonwebtoken": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", + "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", + "requires": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^5.6.0" + }, + "dependencies": { + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + }, + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==" + } + } + }, + "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" + } + }, + "just-extend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.0.2.tgz", + "integrity": "sha512-FrLwOgm+iXrPV+5zDU6Jqu4gCRXbWEQg2O3SKONsWE4w7AXFRkryS53bpWdaL9cNol+AmR3AEYz6kn+o0fCPnw==", + "dev": true + }, + "jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "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" + }, + "dependencies": { + "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" + } + } + } + }, + "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" + } + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" + }, + "latest-version": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-3.1.0.tgz", + "integrity": "sha1-ogU4P+oyKzO1rjsYq+4NwvNW7hU=", + "requires": { + "package-json": "^4.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" + } + }, + "lcov-parse": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/lcov-parse/-/lcov-parse-0.0.10.tgz", + "integrity": "sha1-GwuP+ayceIklBYK3C3ExXZ2m2aM=", + "dev": true + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "load-json-file": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", + "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "strip-bom": "^3.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" + }, + "lodash-compat": { + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/lodash-compat/-/lodash-compat-3.10.2.tgz", + "integrity": "sha1-xpQBKKnTD46QLNLPmf0Muk7PwYM=" + }, + "lodash._arraypool": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._arraypool/-/lodash._arraypool-2.4.1.tgz", + "integrity": "sha1-6I7suS4ruEyQZWEv2VigcZzUf5Q=" + }, + "lodash._basebind": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._basebind/-/lodash._basebind-2.4.1.tgz", + "integrity": "sha1-6UC5690nwyfgqNqxtVkWxTQelXU=", + "requires": { + "lodash._basecreate": "~2.4.1", + "lodash._setbinddata": "~2.4.1", + "lodash._slice": "~2.4.1", + "lodash.isobject": "~2.4.1" + } + }, + "lodash._baseclone": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._baseclone/-/lodash._baseclone-2.4.1.tgz", + "integrity": "sha1-MPgj5X4X43NdODvWK2Czh1Q7QYY=", + "requires": { + "lodash._getarray": "~2.4.1", + "lodash._releasearray": "~2.4.1", + "lodash._slice": "~2.4.1", + "lodash.assign": "~2.4.1", + "lodash.foreach": "~2.4.1", + "lodash.forown": "~2.4.1", + "lodash.isarray": "~2.4.1", + "lodash.isobject": "~2.4.1" + } + }, + "lodash._basecreate": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._basecreate/-/lodash._basecreate-2.4.1.tgz", + "integrity": "sha1-+Ob1tXip405UEXm1a47uv0oofgg=", + "requires": { + "lodash._isnative": "~2.4.1", + "lodash.isobject": "~2.4.1", + "lodash.noop": "~2.4.1" + } + }, + "lodash._basecreatecallback": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._basecreatecallback/-/lodash._basecreatecallback-2.4.1.tgz", + "integrity": "sha1-fQsmdknLKeehOdAQO3wR+uhOSFE=", + "requires": { + "lodash._setbinddata": "~2.4.1", + "lodash.bind": "~2.4.1", + "lodash.identity": "~2.4.1", + "lodash.support": "~2.4.1" + } + }, + "lodash._basecreatewrapper": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._basecreatewrapper/-/lodash._basecreatewrapper-2.4.1.tgz", + "integrity": "sha1-TTHy595+E0+/KAN2K4FQsyUZZm8=", + "requires": { + "lodash._basecreate": "~2.4.1", + "lodash._setbinddata": "~2.4.1", + "lodash._slice": "~2.4.1", + "lodash.isobject": "~2.4.1" + } + }, + "lodash._createwrapper": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._createwrapper/-/lodash._createwrapper-2.4.1.tgz", + "integrity": "sha1-UdaVeXPaTtVW43KQ2MGhjFPeFgc=", + "requires": { + "lodash._basebind": "~2.4.1", + "lodash._basecreatewrapper": "~2.4.1", + "lodash._slice": "~2.4.1", + "lodash.isfunction": "~2.4.1" + } + }, + "lodash._getarray": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._getarray/-/lodash._getarray-2.4.1.tgz", + "integrity": "sha1-+vH3+BD6mFolHCGHQESBCUg55e4=", + "requires": { + "lodash._arraypool": "~2.4.1" + } + }, + "lodash._isnative": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._isnative/-/lodash._isnative-2.4.1.tgz", + "integrity": "sha1-PqZAS3hKe+g2x7V1gOHN95sUgyw=" + }, + "lodash._maxpoolsize": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._maxpoolsize/-/lodash._maxpoolsize-2.4.1.tgz", + "integrity": "sha1-nUgvRjuOZq++WcLBTtsRcGAXIzQ=" + }, + "lodash._objecttypes": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._objecttypes/-/lodash._objecttypes-2.4.1.tgz", + "integrity": "sha1-fAt/admKH3ZSn4kLDNsbTf7BHBE=" + }, + "lodash._releasearray": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._releasearray/-/lodash._releasearray-2.4.1.tgz", + "integrity": "sha1-phOWMNdtFTawfdyAliiJsIL2pkE=", + "requires": { + "lodash._arraypool": "~2.4.1", + "lodash._maxpoolsize": "~2.4.1" + } + }, + "lodash._setbinddata": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._setbinddata/-/lodash._setbinddata-2.4.1.tgz", + "integrity": "sha1-98IAzRuS7yNrOZ7s9zxkjReqlNI=", + "requires": { + "lodash._isnative": "~2.4.1", + "lodash.noop": "~2.4.1" + } + }, + "lodash._shimkeys": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._shimkeys/-/lodash._shimkeys-2.4.1.tgz", + "integrity": "sha1-bpzJZm/wgfC1psl4uD4kLmlJ0gM=", + "requires": { + "lodash._objecttypes": "~2.4.1" + } + }, + "lodash._slice": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._slice/-/lodash._slice-2.4.1.tgz", + "integrity": "sha1-dFz0GlNZexj2iImFREBe+isG2Q8=" + }, + "lodash.assign": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-2.4.1.tgz", + "integrity": "sha1-hMOVlt1xGBqXsGUpE6fJZ15Jsao=", + "requires": { + "lodash._basecreatecallback": "~2.4.1", + "lodash._objecttypes": "~2.4.1", + "lodash.keys": "~2.4.1" + } + }, + "lodash.bind": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.bind/-/lodash.bind-2.4.1.tgz", + "integrity": "sha1-XRn6AFyMTSNvr0dCx7eh/Kvikmc=", + "requires": { + "lodash._createwrapper": "~2.4.1", + "lodash._slice": "~2.4.1" + } + }, + "lodash.clonedeep": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-2.4.1.tgz", + "integrity": "sha1-8pIDtAsS/uCkXTYxZIJZvrq8eGg=", + "requires": { + "lodash._baseclone": "~2.4.1", + "lodash._basecreatecallback": "~2.4.1" + } + }, + "lodash.flattendeep": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", + "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", + "dev": true + }, + "lodash.foreach": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.foreach/-/lodash.foreach-2.4.1.tgz", + "integrity": "sha1-/j/Do0yGyUyrb5UiVgKCdB4BYwk=", + "requires": { + "lodash._basecreatecallback": "~2.4.1", + "lodash.forown": "~2.4.1" + } + }, + "lodash.forown": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.forown/-/lodash.forown-2.4.1.tgz", + "integrity": "sha1-eLQer+FAX6lmRZ6kGT/VAtCEUks=", + "requires": { + "lodash._basecreatecallback": "~2.4.1", + "lodash._objecttypes": "~2.4.1", + "lodash.keys": "~2.4.1" + } + }, + "lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=" + }, + "lodash.identity": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.identity/-/lodash.identity-2.4.1.tgz", + "integrity": "sha1-ZpTP+mX++TH3wxzobHRZfPVg9PE=" + }, + "lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" + }, + "lodash.isarray": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-2.4.1.tgz", + "integrity": "sha1-tSoybB9i9tfac6MdVAHfbvRPD6E=", + "requires": { + "lodash._isnative": "~2.4.1" + } + }, + "lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" + }, + "lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=" + }, + "lodash.isfunction": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.isfunction/-/lodash.isfunction-2.4.1.tgz", + "integrity": "sha1-LP1XXHPkmKtX4xm3f6Aq3vE6lNE=" + }, + "lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" + }, + "lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" + }, + "lodash.isobject": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-2.4.1.tgz", + "integrity": "sha1-Wi5H/mmVPx7mMafrof5k0tBlWPU=", + "requires": { + "lodash._objecttypes": "~2.4.1" + } + }, + "lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" + }, + "lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" + }, + "lodash.keys": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-2.4.1.tgz", + "integrity": "sha1-SN6kbfj/djKxDXBrissmWR4rNyc=", + "requires": { + "lodash._isnative": "~2.4.1", + "lodash._shimkeys": "~2.4.1", + "lodash.isobject": "~2.4.1" + } + }, + "lodash.noop": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.noop/-/lodash.noop-2.4.1.tgz", + "integrity": "sha1-T7VPgWZS5a4Q6PcvcXo4jHMmU4o=" + }, + "lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" + }, + "lodash.set": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz", + "integrity": "sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM=" + }, + "lodash.support": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.support/-/lodash.support-2.4.1.tgz", + "integrity": "sha1-Mg4LZwMWc8KNeiu12eAzGkUkBRU=", + "requires": { + "lodash._isnative": "~2.4.1" + } + }, + "log-driver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/log-driver/-/log-driver-1.2.7.tgz", + "integrity": "sha512-U7KCmLdqsGHBLeWqYlFA0V0Sl6P08EE1ZrmA9cxjUE0WVqT9qnyVDPz1kzpFEP0jdJuFnasWIfSd7fsaNXkpbg==", + "dev": true + }, + "log-symbols": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", + "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", + "dev": true, + "requires": { + "chalk": "^2.0.1" + } + }, + "lolex": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-4.0.1.tgz", + "integrity": "sha512-UHuOBZ5jjsKuzbB/gRNNW8Vg8f00Emgskdq2kvZxgBJCS0aqquAuXai/SkWORlKeZEiNQWZjFZOqIUcH9LqKCw==", + "dev": true + }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, + "loud-rejection": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", + "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", + "requires": { + "currently-unhandled": "^0.4.1", + "signal-exit": "^3.0.0" + } + }, + "lowercase-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", + "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==" + }, + "lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "lru-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz", + "integrity": "sha1-Jzi9nw089PhEkMVzbEhpmsYyzaM=", + "requires": { + "es5-ext": "~0.10.2" + } + }, + "make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "requires": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "dependencies": { + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==" + }, + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==" + } + } + }, + "map-age-cleaner": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", + "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", + "requires": { + "p-defer": "^1.0.0" + } + }, + "map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=" + }, + "map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=" + }, + "map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "requires": { + "object-visit": "^1.0.0" + } + }, + "md5": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/md5/-/md5-2.2.1.tgz", + "integrity": "sha1-U6s41f48iJG6RlMp6iP6wFQBJvk=", + "requires": { + "charenc": "~0.0.1", + "crypt": "~0.0.1", + "is-buffer": "~1.1.1" + } + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + }, + "mem": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", + "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", + "requires": { + "map-age-cleaner": "^0.1.1", + "mimic-fn": "^2.0.0", + "p-is-promise": "^2.0.0" + }, + "dependencies": { + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" + } + } + }, + "memoizee": { + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.14.tgz", + "integrity": "sha512-/SWFvWegAIYAO4NQMpcX+gcra0yEZu4OntmUdrBaWrJncxOqAziGFlHxc7yjKVK2uu3lpPW27P27wkR82wA8mg==", + "requires": { + "d": "1", + "es5-ext": "^0.10.45", + "es6-weak-map": "^2.0.2", + "event-emitter": "^0.3.5", + "is-promise": "^2.1", + "lru-queue": "0.1", + "next-tick": "1", + "timers-ext": "^0.1.5" + } + }, + "memory-cache": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/memory-cache/-/memory-cache-0.1.6.tgz", + "integrity": "sha1-LtmTPteoxxgkm+c2b3yodJrPiiQ=" + }, + "meow": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", + "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", + "requires": { + "camelcase-keys": "^2.0.0", + "decamelize": "^1.1.2", + "loud-rejection": "^1.0.0", + "map-obj": "^1.0.1", + "minimist": "^1.1.3", + "normalize-package-data": "^2.3.4", + "object-assign": "^4.0.1", + "read-pkg-up": "^1.0.1", + "redent": "^1.0.0", + "trim-newlines": "^1.0.0" + }, + "dependencies": { + "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" + } + }, + "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" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + }, + "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" + } + }, + "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" + } + }, + "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" + } + } + } + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + }, + "merge-source-map": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/merge-source-map/-/merge-source-map-1.1.0.tgz", + "integrity": "sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw==", + "dev": true, + "requires": { + "source-map": "^0.6.1" + }, + "dependencies": { + "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 + } + } + }, + "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", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "mime": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", + "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==" + }, + "mime-db": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", + "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==" + }, + "mime-types": { + "version": "2.1.18", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", + "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", + "requires": { + "mime-db": "~1.33.0" + } + }, + "mimer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/mimer/-/mimer-1.0.0.tgz", + "integrity": "sha512-4ZJvCzfcwsBgPbkKXUzGoVZMWjv8IDIygkGzVc7uUYhgnK0t2LmGxxjdgH1i+pn0/KQfB5F/VKUJlfyTSOFQjg==" + }, + "mimic-fn": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "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==", + "requires": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "requires": { + "minimist": "0.0.8" + } + }, + "mocha": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-6.1.4.tgz", + "integrity": "sha512-PN8CIy4RXsIoxoFJzS4QNnCH4psUCPWc4/rPrst/ecSJJbLBkubMiyGCP2Kj/9YnWbotFqAoeXyXMucj7gwCFg==", + "dev": true, + "requires": { + "ansi-colors": "3.2.3", + "browser-stdout": "1.3.1", + "debug": "3.2.6", + "diff": "3.5.0", + "escape-string-regexp": "1.0.5", + "find-up": "3.0.0", + "glob": "7.1.3", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "3.13.1", + "log-symbols": "2.2.0", + "minimatch": "3.0.4", + "mkdirp": "0.5.1", + "ms": "2.1.1", + "node-environment-flags": "1.0.5", + "object.assign": "4.1.0", + "strip-json-comments": "2.0.1", + "supports-color": "6.0.0", + "which": "1.3.1", + "wide-align": "1.1.3", + "yargs": "13.2.2", + "yargs-parser": "13.0.0", + "yargs-unparser": "1.5.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "cliui": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", + "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", + "dev": true, + "requires": { + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", + "wrap-ansi": "^2.0.0" + }, + "dependencies": { + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + } + } + }, + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "invert-kv": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", + "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", + "dev": true + }, + "lcid": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", + "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", + "dev": true, + "requires": { + "invert-kv": "^2.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, + "os-locale": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", + "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", + "dev": true, + "requires": { + "execa": "^1.0.0", + "lcid": "^2.0.0", + "mem": "^4.0.0" + } + }, + "p-limit": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", + "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + }, + "dependencies": { + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "supports-color": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz", + "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "dev": true + }, + "yargs": { + "version": "13.2.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.2.2.tgz", + "integrity": "sha512-WyEoxgyTD3w5XRpAQNYUB9ycVH/PQrToaTXdYXRdOXvEy1l19br+VJsc0vcO8PTGg5ro/l/GY7F/JMEBmI0BxA==", + "dev": true, + "requires": { + "cliui": "^4.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "os-locale": "^3.1.0", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.0.0" + } + }, + "yargs-parser": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.0.0.tgz", + "integrity": "sha512-w2LXjoL8oRdRQN+hOyppuXs+V/fVAYtpcrRxZuF7Kt/Oc+Jr2uAcVntaUTNT6w5ihoWfFDpNY8CPx1QskxZ/pw==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + }, + "mocha-lcov-reporter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/mocha-lcov-reporter/-/mocha-lcov-reporter-1.3.0.tgz", + "integrity": "sha1-Rpve9PivyaEWBW8HnfYYLQr7A4Q=", + "dev": true + }, + "moment": { + "version": "2.24.0", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", + "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==" + }, + "moment-timezone": { + "version": "0.5.25", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.25.tgz", + "integrity": "sha512-DgEaTyN/z0HFaVcVbSyVCUU6HeFdnNC3vE4c9cgu2dgMTvjBUBdBzWfasTBmAW45u5OIMeCJtU8yNjM22DHucw==", + "requires": { + "moment": ">= 2.9.0" + } + }, + "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" + } + }, + "mpath": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.5.2.tgz", + "integrity": "sha512-NOeCoW6AYc3hLi30npe7uzbD9b4FQZKH40YKABUCCvaKKL5agj6YzvHoNx8jQpDMNPgIa5bvSZQbQpWBAVD0Kw==" + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "multer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.1.tgz", + "integrity": "sha512-zzOLNRxzszwd+61JFuAo0fxdQfvku12aNJgnla0AQ+hHxFmfc/B7jBVuPr5Rmvu46Jze/iJrFpSOsD7afO8SDw==", + "requires": { + "append-field": "^1.0.0", + "busboy": "^0.2.11", + "concat-stream": "^1.5.2", + "mkdirp": "^0.5.1", + "object-assign": "^4.1.1", + "on-finished": "^2.3.0", + "type-is": "^1.6.4", + "xtend": "^4.0.0" + } + }, + "multer-storage-cloudinary": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/multer-storage-cloudinary/-/multer-storage-cloudinary-2.2.1.tgz", + "integrity": "sha1-0x8luPCVID8xyeVUwBrYxpGxqXQ=", + "requires": { + "run-parallel": "^1.1.6" + } + }, + "mute-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", + "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", + "dev": true + }, + "nan": { + "version": "2.13.2", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.13.2.tgz", + "integrity": "sha512-TghvYc72wlMGMVMluVo9WRJc0mB8KxxF/gZ4YYFy7V2ZQX9l7rgbPg7vjS9mt6U5HXODVFVI2bOduCzwOMv/lw==" + }, + "nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + } + }, + "native-promise-only": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/native-promise-only/-/native-promise-only-0.8.1.tgz", + "integrity": "sha1-IKMYwwy0X3H+et+/eyHJnBRy7xE=" + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "negotiator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" + }, + "neo-async": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.0.tgz", + "integrity": "sha512-MFh0d/Wa7vkKO3Y3LlacqAEeHK0mckVqzDieUKTT+KGxi+zIpeVsFxymkIiRpbpDziHc290Xr9A1O4Om7otoRA==" + }, + "nested-error-stacks": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/nested-error-stacks/-/nested-error-stacks-2.1.0.tgz", + "integrity": "sha512-AO81vsIO1k1sM4Zrd6Hu7regmJN1NSiAja10gc4bX3F0wd+9rQmcuHQaHVQCYIEC8iFXnE+mavh23GOt7wBgug==", + "dev": true + }, + "next-tick": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", + "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=" + }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" + }, + "nise": { + "version": "1.4.10", + "resolved": "https://registry.npmjs.org/nise/-/nise-1.4.10.tgz", + "integrity": "sha512-sa0RRbj53dovjc7wombHmVli9ZihXbXCQ2uH3TNm03DyvOSIQbxg+pbqDKrk2oxMK1rtLGVlKxcB9rrc6X5YjA==", + "dev": true, + "requires": { + "@sinonjs/formatio": "^3.1.0", + "@sinonjs/text-encoding": "^0.7.1", + "just-extend": "^4.0.2", + "lolex": "^2.3.2", + "path-to-regexp": "^1.7.0" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "lolex": { + "version": "2.7.5", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-2.7.5.tgz", + "integrity": "sha512-l9x0+1offnKKIzYVjyXU2SiwhXDLekRzKyhnbyldPHvC7BvLPVpdNUNR2KeMAiCN2D/kLNttZgQD5WjSxuBx3Q==", + "dev": true + }, + "path-to-regexp": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz", + "integrity": "sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=", + "dev": true, + "requires": { + "isarray": "0.0.1" + } + } + } + }, + "node-environment-flags": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.5.tgz", + "integrity": "sha512-VNYPRfGfmZLx0Ye20jWzHUjyTW/c+6Wq+iLhDzUI4XmhrDd9l/FozXV3F2xOaXjvp0co0+v1YSR3CMP6g+VvLQ==", + "requires": { + "object.getownpropertydescriptors": "^2.0.3", + "semver": "^5.7.0" + }, + "dependencies": { + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==" + } + } + }, + "node-gyp": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-3.8.0.tgz", + "integrity": "sha512-3g8lYefrRRzvGeSowdJKAKyks8oUpLEd/DyPV4eMhVlhJ0aNaZqIrNUIPuEWWTAoPqyFkfGrM67MC69baqn6vA==", + "requires": { + "fstream": "^1.0.0", + "glob": "^7.0.3", + "graceful-fs": "^4.1.2", + "mkdirp": "^0.5.0", + "nopt": "2 || 3", + "npmlog": "0 || 1 || 2 || 3 || 4", + "osenv": "0", + "request": "^2.87.0", + "rimraf": "2", + "semver": "~5.3.0", + "tar": "^2.0.0", + "which": "1" + }, + "dependencies": { + "nopt": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", + "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", + "requires": { + "abbrev": "1" + } + }, + "semver": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", + "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=" + }, + "tar": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz", + "integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=", + "requires": { + "block-stream": "*", + "fstream": "^1.0.2", + "inherits": "2" + } + } + } + }, + "node-modules-regexp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz", + "integrity": "sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=" + }, + "node-releases": { + "version": "1.1.22", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.22.tgz", + "integrity": "sha512-O6XpteBuntW1j86mw6LlovBIwTe+sO2+7vi9avQffNeIW4upgnaCVm6xrBWH+KATz7mNNRNNeEpuWB7dT6Cr3w==", + "requires": { + "semver": "^5.3.0" + } + }, + "node-sass": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.12.0.tgz", + "integrity": "sha512-A1Iv4oN+Iel6EPv77/HddXErL2a+gZ4uBeZUy+a8O35CFYTXhgA8MgLCWBtwpGZdCvTvQ9d+bQxX/QC36GDPpQ==", + "requires": { + "async-foreach": "^0.1.3", + "chalk": "^1.1.1", + "cross-spawn": "^3.0.0", + "gaze": "^1.0.0", + "get-stdin": "^4.0.1", + "glob": "^7.0.3", + "in-publish": "^2.0.0", + "lodash": "^4.17.11", + "meow": "^3.7.0", + "mkdirp": "^0.5.1", + "nan": "^2.13.2", + "node-gyp": "^3.8.0", + "npmlog": "^4.0.0", + "request": "^2.88.0", + "sass-graph": "^2.2.4", + "stdout-stream": "^1.4.0", + "true-case-path": "^1.0.2" + }, + "dependencies": { + "ajv": { + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz", + "integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==", + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + }, + "aws4": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "cross-spawn": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-3.0.1.tgz", + "integrity": "sha1-ElYDfsufDF9549bvE14wdwGEuYI=", + "requires": { + "lru-cache": "^4.0.1", + "which": "^1.2.9" + } + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" + }, + "har-validator": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "requires": { + "ajv": "^6.5.5", + "har-schema": "^2.0.0" + } + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "mime-db": { + "version": "1.40.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", + "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==" + }, + "mime-types": { + "version": "2.1.24", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", + "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", + "requires": { + "mime-db": "1.40.0" + } + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" + }, + "request": { + "version": "2.88.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", + "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "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" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + }, + "tough-cookie": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "requires": { + "psl": "^1.1.24", + "punycode": "^1.4.1" + } + } + } + }, + "nodemon": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-1.19.1.tgz", + "integrity": "sha512-/DXLzd/GhiaDXXbGId5BzxP1GlsqtMGM9zTmkWrgXtSqjKmGSbLicM/oAy4FR0YWm14jCHRwnR31AHS2dYFHrg==", + "requires": { + "chokidar": "^2.1.5", + "debug": "^3.1.0", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.0.4", + "pstree.remy": "^1.1.6", + "semver": "^5.5.0", + "supports-color": "^5.2.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.2", + "update-notifier": "^2.5.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.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + } + } + }, + "nopt": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", + "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", + "requires": { + "abbrev": "1" + } + }, + "normalize-package-data": { + "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==", + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" + }, + "npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "requires": { + "path-key": "^2.0.0" + } + }, + "npm-sass": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/npm-sass/-/npm-sass-2.3.0.tgz", + "integrity": "sha512-/Msbyq/rsqMJslcxRrRNdxTR2VUTEXETNsYhPWw/lSb+QgpnDdWi4LOocfEpCleDPLzP7FnkM7w9Bc0mKP3xcg==", + "requires": { + "argh": "^0.1.4", + "bluebird": "^3.5.1", + "camelify": "0.0.2", + "findup": "^0.1.5", + "glob": "^6.0.1", + "node-sass": "^4.9.0" + }, + "dependencies": { + "glob": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz", + "integrity": "sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=", + "requires": { + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "2 || 3", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + } + } + }, + "npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "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=" + }, + "nyc": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/nyc/-/nyc-14.1.1.tgz", + "integrity": "sha512-OI0vm6ZGUnoGZv/tLdZ2esSVzDwUC88SNs+6JoSOMVxA+gKMB8Tk7jBwgemLx4O40lhhvZCVw1C+OYLOBOPXWw==", + "dev": true, + "requires": { + "archy": "^1.0.0", + "caching-transform": "^3.0.2", + "convert-source-map": "^1.6.0", + "cp-file": "^6.2.0", + "find-cache-dir": "^2.1.0", + "find-up": "^3.0.0", + "foreground-child": "^1.5.6", + "glob": "^7.1.3", + "istanbul-lib-coverage": "^2.0.5", + "istanbul-lib-hook": "^2.0.7", + "istanbul-lib-instrument": "^3.3.0", + "istanbul-lib-report": "^2.0.8", + "istanbul-lib-source-maps": "^3.0.6", + "istanbul-reports": "^2.2.4", + "js-yaml": "^3.13.1", + "make-dir": "^2.1.0", + "merge-source-map": "^1.1.0", + "resolve-from": "^4.0.0", + "rimraf": "^2.6.3", + "signal-exit": "^3.0.2", + "spawn-wrap": "^1.4.2", + "test-exclude": "^5.2.3", + "uuid": "^3.3.2", + "yargs": "^13.2.2", + "yargs-parser": "^13.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dev": true, + "requires": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + } + }, + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "invert-kv": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", + "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", + "dev": true + }, + "lcid": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", + "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", + "dev": true, + "requires": { + "invert-kv": "^2.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "os-locale": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", + "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", + "dev": true, + "requires": { + "execa": "^1.0.0", + "lcid": "^2.0.0", + "mem": "^4.0.0" + } + }, + "p-limit": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", + "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + } + }, + "y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "dev": true + }, + "yargs": { + "version": "13.2.4", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.2.4.tgz", + "integrity": "sha512-HG/DWAJa1PAnHT9JAhNa8AbAv3FPaiLzioSjCcmuXXhP8MlpHO5vwls4g4j6n30Z74GVQj8Xa62dWVx1QCGklg==", + "dev": true, + "requires": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "os-locale": "^3.1.0", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.0" + } + }, + "yargs-parser": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.0.tgz", + "integrity": "sha512-Yq+32PrijHRri0vVKQEm+ys8mbqWjLiwQkMFNXEENutzLPP0bE4Lcd4iA3OQY5HF+GD3xXxf0MEHb8E4/SA3AA==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + }, + "oauth": { + "version": "0.9.15", + "resolved": "https://registry.npmjs.org/oauth/-/oauth-0.9.15.tgz", + "integrity": "sha1-vR/vr2hslrdUda7VGWQS/2DPucE=" + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "object-component": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz", + "integrity": "sha1-8MaapQ78lbhmwYb0AKM3acsvEpE=" + }, + "object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "requires": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "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" + } + } + } + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" + }, + "object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "requires": { + "isobject": "^3.0.0" + } + }, + "object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + } + }, + "object.entries": { + "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", + "function-bind": "^1.1.1", + "has": "^1.0.3" + } + }, + "object.getownpropertydescriptors": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz", + "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=", + "requires": { + "define-properties": "^1.1.2", + "es-abstract": "^1.5.1" + } + }, + "object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "requires": { + "isobject": "^3.0.1" + } + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "requires": { + "ee-first": "1.1.1" + } + }, + "on-headers": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.1.tgz", + "integrity": "sha1-ko9dD0cNSTQmUepnlLCFfBAGk/c=" + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "dev": true, + "requires": { + "mimic-fn": "^1.0.0" + } + }, + "ono": { + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/ono/-/ono-4.0.11.tgz", + "integrity": "sha512-jQ31cORBFE6td25deYeD80wxKBMj+zBmHTrVxnc6CKhx8gho6ipmWM5zj/oeoqioZ99yqBls9Z/9Nss7J26G2g==", + "requires": { + "format-util": "^1.0.3" + } + }, + "open": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/open/-/open-6.3.0.tgz", + "integrity": "sha512-6AHdrJxPvAXIowO/aIaeHZ8CeMdDf7qCyRNq8NwJpinmCdXhz+NZR7ie1Too94lpciCDsG+qHGO9Mt0svA4OqA==", + "requires": { + "is-wsl": "^1.1.0" + } + }, + "openapi-schema-validation": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/openapi-schema-validation/-/openapi-schema-validation-0.4.2.tgz", + "integrity": "sha512-K8LqLpkUf2S04p2Nphq9L+3bGFh/kJypxIG2NVGKX0ffzT4NQI9HirhiY6Iurfej9lCu7y4Ndm4tv+lm86Ck7w==", + "requires": { + "jsonschema": "1.2.4", + "jsonschema-draft4": "^1.0.0", + "swagger-schema-official": "2.0.0-bab6bed" + } + }, + "openid": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/openid/-/openid-0.5.13.tgz", + "integrity": "sha1-G462yox67m3WJktp2vua14UsKk0=" + }, + "optimist": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", + "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", + "requires": { + "minimist": "~0.0.1", + "wordwrap": "~0.0.2" + }, + "dependencies": { + "wordwrap": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=" + } + } + }, + "optionator": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", + "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", + "dev": true, + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.4", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "wordwrap": "~1.0.0" + } + }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" + }, + "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" + } + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" + }, + "osenv": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "output-file-sync": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/output-file-sync/-/output-file-sync-2.0.1.tgz", + "integrity": "sha512-mDho4qm7WgIXIGf4eYU1RHN2UU5tPfVYVSRwDJw0uTmj35DQUt/eNp19N7v6T3SrR0ESTEf2up2CGO73qI35zQ==", + "requires": { + "graceful-fs": "^4.1.11", + "is-plain-obj": "^1.1.0", + "mkdirp": "^0.5.1" + } + }, + "p-defer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", + "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=" + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" + }, + "p-is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz", + "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==" + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true + }, + "package-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-3.0.0.tgz", + "integrity": "sha512-lOtmukMDVvtkL84rJHI7dpTYq+0rli8N2wlnqUcBuDWCfVhRUfOmnR9SsoHFMLpACvEV60dX7rd0rFaYDZI+FA==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.15", + "hasha": "^3.0.0", + "lodash.flattendeep": "^4.4.0", + "release-zalgo": "^1.0.0" + } + }, + "package-json": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/package-json/-/package-json-4.0.1.tgz", + "integrity": "sha1-iGmgQBJTZhxMTKPabCEh7VVfXu0=", + "requires": { + "got": "^6.7.1", + "registry-auth-token": "^3.0.1", + "registry-url": "^3.0.3", + "semver": "^5.1.0" + } + }, + "packet-reader": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz", + "integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==" + }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.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" + } + }, + "parse-passwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", + "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=" + }, + "parseqs": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz", + "integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=", + "requires": { + "better-assert": "~1.0.0" + } + }, + "parseuri": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.5.tgz", + "integrity": "sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=", + "requires": { + "better-assert": "~1.0.0" + } + }, + "parseurl": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", + "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=" + }, + "pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=" + }, + "passport": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/passport/-/passport-0.4.0.tgz", + "integrity": "sha1-xQlWkTR71a07XhgCOMORTRbwWBE=", + "requires": { + "passport-strategy": "1.x.x", + "pause": "0.0.1" + } + }, + "passport-facebook": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/passport-facebook/-/passport-facebook-3.0.0.tgz", + "integrity": "sha512-K/qNzuFsFISYAyC1Nma4qgY/12V3RSLFdFVsPKXiKZt434wOvthFW1p7zKa1iQihQMRhaWorVE1o3Vi1o+ZgeQ==", + "requires": { + "passport-oauth2": "1.x.x" + } + }, + "passport-google": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/passport-google/-/passport-google-0.3.0.tgz", + "integrity": "sha1-xbS0FiZYiSs3Flt5WgH/1Mfhn7c=", + "requires": { + "passport-openid": "0.3.x", + "pkginfo": "0.2.x" + } + }, + "passport-google-oauth": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/passport-google-oauth/-/passport-google-oauth-2.0.0.tgz", + "integrity": "sha512-JKxZpBx6wBQXX1/a1s7VmdBgwOugohH+IxCy84aPTZNq/iIPX6u7Mqov1zY7MKRz3niFPol0KJz8zPLBoHKtYA==", + "requires": { + "passport-google-oauth1": "1.x.x", + "passport-google-oauth20": "2.x.x" + } + }, + "passport-google-oauth1": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/passport-google-oauth1/-/passport-google-oauth1-1.0.0.tgz", + "integrity": "sha1-r3SoA99R7GRvZqRNgigr5vEI4Mw=", + "requires": { + "passport-oauth1": "1.x.x" + } + }, + "passport-google-oauth20": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/passport-google-oauth20/-/passport-google-oauth20-2.0.0.tgz", + "integrity": "sha512-KSk6IJ15RoxuGq7D1UKK/8qKhNfzbLeLrG3gkLZ7p4A6DBCcv7xpyQwuXtWdpyR0+E0mwkpjY1VfPOhxQrKzdQ==", + "requires": { + "passport-oauth2": "1.x.x" + } + }, + "passport-google-plus": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/passport-google-plus/-/passport-google-plus-0.5.1.tgz", + "integrity": "sha1-EmCcrIMDc3f9wwPnsXxGnNbVL/I=", + "requires": { + "async": "^0.9.x", + "googleapis": "~1.0.0", + "green-jwt": "^0.0.8-alpha", + "passport": "^0.2.x", + "request": "^2.2.x", + "xtend": "^2.x" + }, + "dependencies": { + "async": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz", + "integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=" + }, + "passport": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/passport/-/passport-0.2.2.tgz", + "integrity": "sha1-nDjxe+uSnz2Br3uIOOhDDbhwPys=", + "requires": { + "passport-strategy": "1.x.x", + "pause": "0.0.1" + } + }, + "xtend": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-2.2.0.tgz", + "integrity": "sha1-7vax8ZjByN6vrYsXZaBNrUoBxak=" + } + } + }, + "passport-jwt": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/passport-jwt/-/passport-jwt-4.0.0.tgz", + "integrity": "sha512-BwC0n2GP/1hMVjR4QpnvqA61TxenUMlmfNjYNgK0ZAs0HK4SOQkHcSv4L328blNTLtHq7DbmvyNJiH+bn6C5Mg==", + "requires": { + "jsonwebtoken": "^8.2.0", + "passport-strategy": "^1.0.0" + } + }, + "passport-local": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/passport-local/-/passport-local-1.0.0.tgz", + "integrity": "sha1-H+YyaMkudWBmJkN+O5BmYsFbpu4=", + "requires": { + "passport-strategy": "1.x.x" + } + }, + "passport-oauth1": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/passport-oauth1/-/passport-oauth1-1.1.0.tgz", + "integrity": "sha1-p96YiiEfnPRoc3cTDqdN8ycwyRg=", + "requires": { + "oauth": "0.9.x", + "passport-strategy": "1.x.x", + "utils-merge": "1.x.x" + } + }, + "passport-oauth2": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/passport-oauth2/-/passport-oauth2-1.5.0.tgz", + "integrity": "sha512-kqBt6vR/5VlCK8iCx1/KpY42kQ+NEHZwsSyt4Y6STiNjU+wWICG1i8ucc1FapXDGO15C5O5VZz7+7vRzrDPXXQ==", + "requires": { + "base64url": "3.x.x", + "oauth": "0.9.x", + "passport-strategy": "1.x.x", + "uid2": "0.0.x", + "utils-merge": "1.x.x" + } + }, + "passport-openid": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/passport-openid/-/passport-openid-0.3.1.tgz", + "integrity": "sha1-5k3gSFn6UbZSlIAXQSJiHgj6Iss=", + "requires": { + "openid": "0.5.x", + "passport": "~0.1.3", + "pkginfo": "0.2.x" + }, + "dependencies": { + "passport": { + "version": "0.1.18", + "resolved": "https://registry.npmjs.org/passport/-/passport-0.1.18.tgz", + "integrity": "sha1-yCZEedy2QUytu2Z1LRKzfgtlJaE=", + "requires": { + "pause": "0.0.1", + "pkginfo": "0.2.x" + } + } + } + }, + "passport-strategy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", + "integrity": "sha1-tVOaqPwiWj0a0XlHbd8ja0QPUuQ=" + }, + "passport-twitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/passport-twitter/-/passport-twitter-1.0.4.tgz", + "integrity": "sha1-AaeZ4fdgvy3knyul+6MigvGJMtc=", + "requires": { + "passport-oauth1": "1.x.x", + "xtraverse": "0.1.x" + } + }, + "path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=" + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" + }, + "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=" + }, + "path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=" + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" + }, + "path-loader": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/path-loader/-/path-loader-1.0.10.tgz", + "integrity": "sha512-CMP0v6S6z8PHeJ6NFVyVJm6WyJjIwFvyz2b0n2/4bKdS/0uZa/9sKUlYZzubrn3zuDRU0zIuEDX9DZYQ2ZI8TA==", + "requires": { + "native-promise-only": "^0.8.1", + "superagent": "^3.8.3" + } + }, + "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==" + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + }, + "path-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", + "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", + "dev": true, + "requires": { + "pify": "^2.0.0" + } + }, + "pathval": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", + "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=", + "dev": true + }, + "pause": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", + "integrity": "sha1-HUCLP9t2kjuVQ9lvtMnf1TXZy10=" + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + }, + "pg": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/pg/-/pg-7.11.0.tgz", + "integrity": "sha512-YO4V7vCmEMGoF390LJaFaohWNKaA2ayoQOEZmiHVcAUF+YsRThpf/TaKCgSvsSE7cDm37Q/Cy3Gz41xiX/XjTw==", + "requires": { + "buffer-writer": "2.0.0", + "packet-reader": "1.0.0", + "pg-connection-string": "0.1.3", + "pg-pool": "^2.0.4", + "pg-types": "~2.0.0", + "pgpass": "1.x", + "semver": "4.3.2" + }, + "dependencies": { + "semver": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-4.3.2.tgz", + "integrity": "sha1-x6BxWKgL7dBSNVt3DYLWZA+AO+c=" + } + } + }, + "pg-connection-string": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-0.1.3.tgz", + "integrity": "sha1-2hhHsglA5C7hSSvq9l1J2RskXfc=" + }, + "pg-hstore": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/pg-hstore/-/pg-hstore-2.3.2.tgz", + "integrity": "sha1-9+8FPnubiSrphq8vfL6GQy388k8=", + "requires": { + "underscore": "^1.7.0" + } + }, + "pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==" + }, + "pg-pool": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-2.0.6.tgz", + "integrity": "sha512-hod2zYQxM8Gt482q+qONGTYcg/qVcV32VHVPtktbBJs0us3Dj7xibISw0BAAXVMCzt8A/jhfJvpZaxUlqtqs0g==" + }, + "pg-types": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.0.1.tgz", + "integrity": "sha512-b7y6QM1VF5nOeX9ukMQ0h8a9z89mojrBHXfJeSug4mhL0YpxNBm83ot2TROyoAmX/ZOX3UbwVO4EbH7i1ZZNiw==", + "requires": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + } + }, + "pgpass": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.2.tgz", + "integrity": "sha1-Knu0G2BltnkH6R2hsHwYR8h3swY=", + "requires": { + "split": "^1.0.0" + } + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" + }, + "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", + "integrity": "sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA==", + "requires": { + "node-modules-regexp": "^1.0.0" + } + }, + "pkg-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", + "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", + "dev": true, + "requires": { + "find-up": "^2.1.0" + } + }, + "pkginfo": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/pkginfo/-/pkginfo-0.2.3.tgz", + "integrity": "sha1-cjnEKl72wwuPMoQ52bn/cQQkkPg=" + }, + "posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=" + }, + "postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==" + }, + "postgres-bytea": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", + "integrity": "sha1-AntTPAqokOJtFy1Hz5zOzFIazTU=" + }, + "postgres-date": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.4.tgz", + "integrity": "sha512-bESRvKVuTrjoBluEcpv2346+6kgB7UlnqWZsnbnCccTNq/pqfj1j6oBaN5+b/NrDXepYUT/HKadqv3iS9lJuVA==" + }, + "postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "requires": { + "xtend": "^4.0.0" + } + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true + }, + "prepend-http": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", + "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=" + }, + "private": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", + "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==" + }, + "process-nextick-args": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" + }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true + }, + "proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=" + }, + "proxy-addr": { + "version": "2.0.5", + "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" + } + }, + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" + }, + "psl": { + "version": "1.1.31", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.31.tgz", + "integrity": "sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw==" + }, + "pstree.remy": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.7.tgz", + "integrity": "sha512-xsMgrUwRpuGskEzBFkH8NmTimbZ5PcPup0LA8JJkHIm2IMUbQcpo3yeLNWVrufEYjh8YwtSVh0xz6UeWc5Oh5A==" + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + }, + "q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=" + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + }, + "random-bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", + "integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs=" + }, + "range-parser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", + "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=" + }, + "raw-body": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", + "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", + "requires": { + "bytes": "3.1.0", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "dependencies": { + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + } + } + }, + "rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + } + } + }, + "read-pkg": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", + "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", + "dev": true, + "requires": { + "load-json-file": "^2.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^2.0.0" + } + }, + "read-pkg-up": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", + "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", + "dev": true, + "requires": { + "find-up": "^2.0.0", + "read-pkg": "^2.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", + "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "requires": { + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" + } + }, + "readline2": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/readline2/-/readline2-1.0.1.tgz", + "integrity": "sha1-QQWWCP/BVHV7cV2ZidGZ/783LjU=", + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "mute-stream": "0.0.5" + }, + "dependencies": { + "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" + } + }, + "mute-stream": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.5.tgz", + "integrity": "sha1-j7+rsKmKJT0xhDMfno3rc3L6xsA=" + } + } + }, + "redent": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", + "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", + "requires": { + "indent-string": "^2.1.0", + "strip-indent": "^1.0.1" + } + }, + "reduce-component": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/reduce-component/-/reduce-component-1.0.1.tgz", + "integrity": "sha1-4Mk1QsV0UhvqE98PlIjtgqt3xdo=" + }, + "regenerate": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz", + "integrity": "sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==" + }, + "regenerate-unicode-properties": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-8.1.0.tgz", + "integrity": "sha512-LGZzkgtLY79GeXLm8Dp0BVLdQlWICzBnJz/ipWUgo59qBaZ+BHtq51P2q1uVZlppMuUAT37SDk39qUbjTWB7bA==", + "requires": { + "regenerate": "^1.4.0" + } + }, + "regenerator-runtime": { + "version": "0.13.2", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.2.tgz", + "integrity": "sha512-S/TQAZJO+D3m9xeN1WTI8dLKBBiRgXBlTJvbWjCThHWZj9EvHK70Ff50/tYj2J/fvBY6JtFVwRuazHN2E7M9BA==" + }, + "regenerator-transform": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.0.tgz", + "integrity": "sha512-rtOelq4Cawlbmq9xuMR5gdFmv7ku/sFoB7sRiywx7aq53bc52b4j6zvH7Te1Vt/X2YveDKnCGUbioieU7FEL3w==", + "requires": { + "private": "^0.1.6" + } + }, + "regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "requires": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + } + }, + "regexp-tree": { + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.10.tgz", + "integrity": "sha512-K1qVSbcedffwuIslMwpe6vGlj+ZXRnGkvjAtFHfDZZZuEdA/h0dxljAPu9vhUo6Rrx2U2AwJ+nSQ6hK+lrP5MQ==" + }, + "regexpp": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", + "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", + "dev": true + }, + "regexpu-core": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.5.4.tgz", + "integrity": "sha512-BtizvGtFQKGPUcTy56o3nk1bGRp4SZOTYrDtGNlqCQufptV5IkkLN6Emw+yunAJjzf+C9FQFtvq7IoA3+oMYHQ==", + "requires": { + "regenerate": "^1.4.0", + "regenerate-unicode-properties": "^8.0.2", + "regjsgen": "^0.5.0", + "regjsparser": "^0.6.0", + "unicode-match-property-ecmascript": "^1.0.4", + "unicode-match-property-value-ecmascript": "^1.1.0" + } + }, + "registry-auth-token": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.4.0.tgz", + "integrity": "sha512-4LM6Fw8eBQdwMYcES4yTnn2TqIasbXuwDx3um+QRs7S55aMKCBKBxvPXl2RiUjHwuJLTyYfxSpmfSAjQpcuP+A==", + "requires": { + "rc": "^1.1.6", + "safe-buffer": "^5.0.1" + } + }, + "registry-url": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz", + "integrity": "sha1-PU74cPc93h138M+aOBQyRE4XSUI=", + "requires": { + "rc": "^1.0.1" + } + }, + "regjsgen": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.0.tgz", + "integrity": "sha512-RnIrLhrXCX5ow/E5/Mh2O4e/oa1/jW0eaBKTSy3LaCj+M3Bqvm97GWDp2yUtzIs4LEn65zR2yiYGFqb2ApnzDA==" + }, + "regjsparser": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.0.tgz", + "integrity": "sha512-RQ7YyokLiQBomUJuUG8iGVvkgOLxwyZM8k6d3q5SAXpg4r5TZJZigKFvC6PpD+qQ98bCDC5YelPeA3EucDoNeQ==", + "requires": { + "jsesc": "~0.5.0" + }, + "dependencies": { + "jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=" + } + } + }, + "release-zalgo": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", + "integrity": "sha1-CXALflB0Mpc5Mw5TXFqQ+2eFFzA=", + "dev": true, + "requires": { + "es6-error": "^4.0.1" + } + }, + "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=" + }, + "repeat-element": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", + "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==" + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=" + }, + "repeating": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", + "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", + "requires": { + "is-finite": "^1.0.0" + } + }, + "request": { + "version": "2.88.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", + "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "dependencies": { + "mime-db": { + "version": "1.40.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", + "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==" + }, + "mime-types": { + "version": "2.1.24", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", + "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", + "requires": { + "mime-db": "1.40.0" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "tough-cookie": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "requires": { + "psl": "^1.1.24", + "punycode": "^1.4.1" + } + } + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" + }, + "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=" + }, + "resolve": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.10.1.tgz", + "integrity": "sha512-KuIe4mf++td/eFb6wkaPbMDnP6kObCaEtIDuHOUED6MNUo4K670KZUHuuvYPZDxNF0WVLw49n06M2m2dXphEzA==", + "requires": { + "path-parse": "^1.0.6" + } + }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + }, + "resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=" + }, + "restore-cursor": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", + "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", + "dev": true, + "requires": { + "onetime": "^2.0.0", + "signal-exit": "^3.0.2" + } + }, + "ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==" + }, + "retry-as-promised": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-3.2.0.tgz", + "integrity": "sha512-CybGs60B7oYU/qSQ6kuaFmRd9sTZ6oXSc0toqePvV74Ac6/IFZSI1ReFQmtCN+uvW1Mtqdwpvt/LGOiCBAY2Mg==", + "requires": { + "any-promise": "^1.3.0" + } + }, + "rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "requires": { + "glob": "^7.1.3" + } + }, + "rosie": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/rosie/-/rosie-2.0.1.tgz", + "integrity": "sha1-wlDEeHzkULcqqe/yZQn2hYmBT6I=" + }, + "run-async": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", + "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", + "dev": true, + "requires": { + "is-promise": "^2.1.0" + } + }, + "run-parallel": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.9.tgz", + "integrity": "sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q==" + }, + "rx-lite": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-3.1.2.tgz", + "integrity": "sha1-Gc5QLKVyZl87ZHsQk5+X/RYV8QI=" + }, + "rxjs": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.1.tgz", + "integrity": "sha512-y0j31WJc83wPu31vS1VlAFW5JGrnGC+j+TtGAa1fRQphy48+fDYiDmX8tjGloToEsMkxnouOg/1IzXGKkJnZMg==", + "requires": { + "tslib": "^1.9.0" + } + }, + "safe-buffer": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" + }, + "safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "requires": { + "ret": "~0.1.10" + } + }, + "safer-buffer": { + "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, - "saslprep": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.0.tgz", - "integrity": "sha512-5lvKUEQ7lAN5/vPl5d3k8FQeDbEamu9kizfATfLLWV5h6Mkh1xcieR1FSsJkcSRUk49lF2tAW8gzXWVwtwZVhw==", - "optional": true + "sanitize-filename": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.1.tgz", + "integrity": "sha1-YS2hyWRz+gLczaktzVtKsWSmdyo=", + "requires": { + "truncate-utf8-bytes": "^1.0.0" + } + }, + "sass-graph": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/sass-graph/-/sass-graph-2.2.4.tgz", + "integrity": "sha1-E/vWPNHK8JCLn9k0dq1DpR0eC0k=", + "requires": { + "glob": "^7.0.0", + "lodash": "^4.0.0", + "scss-tokenizer": "^0.2.3", + "yargs": "^7.0.0" + } + }, + "scss-tokenizer": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz", + "integrity": "sha1-jrBtualyMzOCTT9VMGQRSYR85dE=", + "requires": { + "js-base64": "^2.1.8", + "source-map": "^0.4.2" + }, + "dependencies": { + "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" + } + } + } + }, + "semver": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", + "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==" + }, + "semver-diff": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-2.1.0.tgz", + "integrity": "sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY=", + "requires": { + "semver": "^5.0.3" + } + }, + "send": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", + "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", + "requires": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.6.2", + "mime": "1.4.1", + "ms": "2.0.0", + "on-finished": "~2.3.0", + "range-parser": "~1.2.0", + "statuses": "~1.4.0" + }, + "dependencies": { + "http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + } + }, + "statuses": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" + } + } + }, + "sequelize": { + "version": "5.8.7", + "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-5.8.7.tgz", + "integrity": "sha512-1rubZM8fAyCt5ipyS+3HJ3Jbmb8WesLdPJ3jIbTD+78EbuPZILFEA5fK0mliVRBx7oM7oPULeVX0lxSRXBV1jw==", + "requires": { + "bluebird": "^3.5.0", + "cls-bluebird": "^2.1.0", + "debug": "^4.1.1", + "dottie": "^2.0.0", + "inflection": "1.12.0", + "lodash": "^4.17.11", + "moment": "^2.24.0", + "moment-timezone": "^0.5.21", + "retry-as-promised": "^3.1.0", + "semver": "^5.6.0", + "sequelize-pool": "^1.0.2", + "toposort-class": "^1.0.1", + "uuid": "^3.2.1", + "validator": "^10.11.0", + "wkx": "^0.4.6" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + }, + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==" + } + } + }, + "sequelize-cli": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/sequelize-cli/-/sequelize-cli-5.4.0.tgz", + "integrity": "sha512-4Gvl0yH0T3hhSdiiOci3+IKIfVG9x2os0hGWsbfa8QuyGgk9mZOqgTBnSCRtuxsdAyzUix9kfcTnfNolVNtprg==", + "requires": { + "bluebird": "^3.5.3", + "cli-color": "^1.4.0", + "fs-extra": "^7.0.1", + "js-beautify": "^1.8.8", + "lodash": "^4.17.5", + "resolve": "^1.5.0", + "umzug": "^2.1.0", + "yargs": "^12.0.5" + }, + "dependencies": { + "bluebird": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.4.tgz", + "integrity": "sha512-FG+nFEZChJrbQ9tIccIfZJBz3J7mLrAhxakAbnrJWn8d7aKOC+LWifa0G+p4ZqKp4y13T7juYvdhq9NzKdsrjw==" + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" + }, + "cliui": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", + "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", + "requires": { + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", + "wrap-ansi": "^2.0.0" + } + }, + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "requires": { + "locate-path": "^3.0.0" + } + }, + "fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "requires": { + "pump": "^3.0.0" + } + }, + "invert-kv": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", + "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==" + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "lcid": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", + "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", + "requires": { + "invert-kv": "^2.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "os-locale": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", + "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", + "requires": { + "execa": "^1.0.0", + "lcid": "^2.0.0", + "mem": "^4.0.0" + } + }, + "p-limit": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", + "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" + }, + "yargs": { + "version": "12.0.5", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", + "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", + "requires": { + "cliui": "^4.0.0", + "decamelize": "^1.2.0", + "find-up": "^3.0.0", + "get-caller-file": "^1.0.1", + "os-locale": "^3.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1 || ^4.0.0", + "yargs-parser": "^11.1.1" + } + }, + "yargs-parser": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", + "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + }, + "sequelize-easy-query": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/sequelize-easy-query/-/sequelize-easy-query-1.1.0.tgz", + "integrity": "sha512-n+n37QwJTUwfbgMMu1ulZe8CU5rDlQAzpqyU9xjA5JNNrXp6LIdK0rq/RmhWrwBISZw84YiIy8Iu0ZZUixx/Mg==" + }, + "sequelize-pool": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/sequelize-pool/-/sequelize-pool-1.0.2.tgz", + "integrity": "sha512-VMKl/gCCdIvB1gFZ7p+oqLFEyZEz3oMMYjkKvfEC7GoO9bBcxmfOOU9RdkoltfXGgBZFigSChihRly2gKtsh2w==", + "requires": { + "bluebird": "^3.5.3" + }, + "dependencies": { + "bluebird": { + "version": "3.5.5", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.5.tgz", + "integrity": "sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w==" + } + } + }, + "sequelize-test-helpers": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/sequelize-test-helpers/-/sequelize-test-helpers-1.1.1.tgz", + "integrity": "sha512-ECWyCmtDHTUaFxQHZIeUNVg9zdv13sZR+vMYBouCCvfnOXl1FwuPBwnwywzCwuXTx9FD03AMm6Ay0T0OYWyfAA==" + }, + "serve-static": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz", + "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==", + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.2", + "send": "0.16.2" + } + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + }, + "set-value": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz", + "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==", + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" + }, + "shimmer": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.2.1.tgz", + "integrity": "sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==" + }, + "sigmund": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", + "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=" + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" + }, + "sinon": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-7.3.2.tgz", + "integrity": "sha512-thErC1z64BeyGiPvF8aoSg0LEnptSaWE7YhdWWbWXgelOyThent7uKOnnEh9zBxDbKixtr5dEko+ws1sZMuFMA==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.4.0", + "@sinonjs/formatio": "^3.2.1", + "@sinonjs/samsam": "^3.3.1", + "diff": "^3.5.0", + "lolex": "^4.0.1", + "nise": "^1.4.10", + "supports-color": "^5.5.0" + } + }, + "sinon-chai": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/sinon-chai/-/sinon-chai-3.3.0.tgz", + "integrity": "sha512-r2JhDY7gbbmh5z3Q62pNbrjxZdOAjpsqW/8yxAZRSqLZqowmfGZPGUZPFf3UX36NLis0cv8VEM5IJh9HgkSOAA==", + "dev": true + }, + "slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==" + }, + "slice-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", + "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "astral-regex": "^1.0.0", + "is-fullwidth-code-point": "^2.0.0" + } + }, + "slug": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/slug/-/slug-1.1.0.tgz", + "integrity": "sha512-NuIOjDQeTMPm+/AUIHJ5636mF3jOsYLFnoEErl9Tdpt4kpt4fOrAJxscH9mUgX1LtPaEqgPCawBg7A4yhoSWRg==", + "requires": { + "unicode": ">= 0.3.1" + } + }, + "snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "requires": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "requires": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "requires": { + "kind-of": "^3.2.0" + }, + "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" + } + } + } + }, + "sntp": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/sntp/-/sntp-0.2.4.tgz", + "integrity": "sha1-+4hfGLDzqtGJ+CSGJTa87ux1CQA=", + "requires": { + "hoek": "0.9.x" + }, + "dependencies": { + "hoek": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-0.9.1.tgz", + "integrity": "sha1-PTIkYrrfB3Fup+uFuviAec3c5QU=" + } + } + }, + "socket.io": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.2.0.tgz", + "integrity": "sha512-wxXrIuZ8AILcn+f1B4ez4hJTPG24iNgxBBDaJfT6MsyOhVYiTXWexGoPkd87ktJG8kQEcL/NBvRi64+9k4Kc0w==", + "requires": { + "debug": "~4.1.0", + "engine.io": "~3.3.1", + "has-binary2": "~1.0.2", + "socket.io-adapter": "~1.1.0", + "socket.io-client": "2.2.0", + "socket.io-parser": "~3.3.0" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + } + } + }, + "socket.io-adapter": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-1.1.1.tgz", + "integrity": "sha1-KoBeihTWNyEk3ZFZrUUC+MsH8Gs=" + }, + "socket.io-client": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.2.0.tgz", + "integrity": "sha512-56ZrkTDbdTLmBIyfFYesgOxsjcLnwAKoN4CiPyTVkMQj3zTUh0QAx3GbvIvLpFEOvQWu92yyWICxB0u7wkVbYA==", + "requires": { + "backo2": "1.0.2", + "base64-arraybuffer": "0.1.5", + "component-bind": "1.0.0", + "component-emitter": "1.2.1", + "debug": "~3.1.0", + "engine.io-client": "~3.3.1", + "has-binary2": "~1.0.2", + "has-cors": "1.1.0", + "indexof": "0.0.1", + "object-component": "0.0.3", + "parseqs": "0.0.5", + "parseuri": "0.0.5", + "socket.io-parser": "~3.3.0", + "to-array": "0.1.4" + }, + "dependencies": { + "component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=" + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + } + } + }, + "socket.io-parser": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.3.0.tgz", + "integrity": "sha512-hczmV6bDgdaEbVqhAeVMM/jfUfzuEZHsQg6eOmLgJht6G3mPKMxYm75w2+qhAQZ+4X+1+ATZ+QFKeOZD5riHng==", + "requires": { + "component-emitter": "1.2.1", + "debug": "~3.1.0", + "isarray": "2.0.1" + }, + "dependencies": { + "component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=" + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + }, + "isarray": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", + "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=" + } + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + }, + "source-map-resolve": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", + "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", + "requires": { + "atob": "^2.1.1", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, + "source-map-support": { + "version": "0.5.12", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.12.tgz", + "integrity": "sha512-4h2Pbvyy15EE02G+JOZpUCmqWJuqrs+sEkzewTm++BPi7Hvn/HwcqLAcNxYAyI0x13CpPPn+kMjl+hplXMHITQ==", + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, + "source-map-url": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", + "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=" + }, + "spark-md5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/spark-md5/-/spark-md5-1.0.1.tgz", + "integrity": "sha1-xLmo1Bz3sIRUI6ghgk+N/6D1G3w=" + }, + "spawn-command": { + "version": "0.0.2-1", + "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2-1.tgz", + "integrity": "sha1-YvXpRmmBwbeW3Fkpk34RycaSG9A=" + }, + "spawn-wrap": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-1.4.2.tgz", + "integrity": "sha512-vMwR3OmmDhnxCVxM8M+xO/FtIp6Ju/mNaDfCMMW7FDcLRTPFWUswec4LXJHTJE2hwTI9O0YBfygu4DalFl7Ylg==", + "dev": true, + "requires": { + "foreground-child": "^1.5.6", + "mkdirp": "^0.5.0", + "os-homedir": "^1.0.1", + "rimraf": "^2.6.2", + "signal-exit": "^3.0.2", + "which": "^1.3.0" + } + }, + "spdx-correct": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", + "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", + "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==", + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "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==" + }, + "split": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", + "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", + "requires": { + "through": "2" + } + }, + "split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "requires": { + "extend-shallow": "^3.0.0" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" + }, + "sshpk": { + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.2.tgz", + "integrity": "sha1-xvxhZIo9nE52T9P8306hBeSSupg=", + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "requires": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" + }, + "stdout-stream": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/stdout-stream/-/stdout-stream-1.4.1.tgz", + "integrity": "sha512-j4emi03KXqJWcIeF8eIXkjMFN1Cmb8gUlDYGeBALLPo5qdyTfA9bOtl8m33lRoC+vFMkP3gl0WsDr6+gzxbbTA==", + "requires": { + "readable-stream": "^2.0.1" + } + }, + "streamsearch": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", + "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=" + }, + "string": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/string/-/string-3.3.3.tgz", + "integrity": "sha1-XqIRzZLSKOGEKUmQpsyXs2anfLA=" + }, + "string-template": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/string-template/-/string-template-0.2.1.tgz", + "integrity": "sha1-QpMuWYo1LQH8IuwzZ9nYTuxsmt0=" + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "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" + } + }, + "stringstream": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.6.tgz", + "integrity": "sha512-87GEBAkegbBcweToUrdzf3eLhWNg06FJTebl4BVJz/JgWy8CvEr9dRtX5qWphiynMSQlxxi+QqN0z5T32SLlhA==" + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "requires": { + "ansi-regex": "^3.0.0" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + }, + "strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" + }, + "strip-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", + "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", + "requires": { + "get-stdin": "^4.0.1" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" + }, + "superagent": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-3.8.3.tgz", + "integrity": "sha512-GLQtLMCoEIK4eDv6OGtkOoSMt3D+oq0y3dsxMuYuDvaNUvuT8eFBuLmfR0iYYzHC1e8hpzC6ZsxbuP6DIalMFA==", + "requires": { + "component-emitter": "^1.2.0", + "cookiejar": "^2.1.0", + "debug": "^3.1.0", + "extend": "^3.0.0", + "form-data": "^2.3.1", + "formidable": "^1.2.0", + "methods": "^1.1.1", + "mime": "^1.4.1", + "qs": "^6.5.1", + "readable-stream": "^2.3.5" + }, + "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.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + } + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + }, + "swagger": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/swagger/-/swagger-0.7.5.tgz", + "integrity": "sha1-O+buPTksOwBvx6m1stYMfoNIYP0=", + "requires": { + "async": "^1.2.1", + "commander": "^2.7.1", + "connect": "^3.3.5", + "debug": "^2.1.3", + "fs-extra": "^0.24.0", + "inquirer": "^0.10.0", + "js-yaml": "^3.3.0", + "lodash": "^3.10.0", + "mocha": "^2.2.1", + "nodemon": "^1.3.7", + "serve-static": "^1.9.2", + "swagger-converter": "^0.2.0", + "swagger-editor": "^2.9.2", + "swagger-test-templates": "^1.2.0", + "swagger-tools": "^0.9.0" + }, + "dependencies": { + "ansi-escapes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz", + "integrity": "sha1-06ioOzGapneTZisT52HHkRQiMG4=" + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "cli-cursor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", + "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=", + "requires": { + "restore-cursor": "^1.0.1" + } + }, + "cli-width": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-1.1.1.tgz", + "integrity": "sha1-pNKT72frt7iNSk1CwMzwDE0eNm0=" + }, + "diff": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-1.4.0.tgz", + "integrity": "sha1-fyjS657nsVqX79ic5j3P2qPMur8=" + }, + "figures": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", + "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", + "requires": { + "escape-string-regexp": "^1.0.5", + "object-assign": "^4.1.0" + } + }, + "glob": { + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.11.tgz", + "integrity": "sha1-Spc/Y1uRkPcV0QmH1cAP0oFevj0=", + "requires": { + "inherits": "2", + "minimatch": "0.3" + } + }, + "growl": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.9.2.tgz", + "integrity": "sha1-Dqd0NxXbjY3ixe3hd14bRayFwC8=" + }, + "inquirer": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-0.10.1.tgz", + "integrity": "sha1-6iXkzmnKFF4FyZ5G3P7AXkASWUo=", + "requires": { + "ansi-escapes": "^1.1.0", + "ansi-regex": "^2.0.0", + "chalk": "^1.0.0", + "cli-cursor": "^1.0.1", + "cli-width": "^1.0.1", + "figures": "^1.3.5", + "lodash": "^3.3.1", + "readline2": "^1.0.1", + "run-async": "^0.1.0", + "rx-lite": "^3.1.2", + "strip-ansi": "^3.0.0", + "through": "^2.3.6" + } + }, + "lodash": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", + "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=" + }, + "lru-cache": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", + "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=" + }, + "minimatch": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.3.0.tgz", + "integrity": "sha1-J12O2qxPG7MyZHIInnlJyDlGmd0=", + "requires": { + "lru-cache": "2", + "sigmund": "~1.0.0" + } + }, + "mocha": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-2.5.3.tgz", + "integrity": "sha1-FhvlvetJZ3HrmzV0UFC2IrWu/Fg=", + "requires": { + "commander": "2.3.0", + "debug": "2.2.0", + "diff": "1.4.0", + "escape-string-regexp": "1.0.2", + "glob": "3.2.11", + "growl": "1.9.2", + "jade": "0.26.3", + "mkdirp": "0.5.1", + "supports-color": "1.2.0", + "to-iso-string": "0.0.2" + }, + "dependencies": { + "commander": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.3.0.tgz", + "integrity": "sha1-/UMOiJgy7DU7ms0d4hfBHLPu+HM=" + }, + "debug": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", + "requires": { + "ms": "0.7.1" + } + }, + "escape-string-regexp": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.2.tgz", + "integrity": "sha1-Tbwv5nTnGUnK8/smlc5/LcHZqNE=" + }, + "supports-color": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-1.2.0.tgz", + "integrity": "sha1-/x7R5hFp0Gs88tWI4YixjYhH4X4=" + } + } + }, + "ms": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", + "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=" + }, + "onetime": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", + "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=" + }, + "restore-cursor": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz", + "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=", + "requires": { + "exit-hook": "^1.0.0", + "onetime": "^1.0.0" + } + }, + "run-async": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-0.1.0.tgz", + "integrity": "sha1-yK1KXhEGYeQCp9IbUw4AnyX444k=", + "requires": { + "once": "^1.3.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" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + } + } + }, + "swagger-converter": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/swagger-converter/-/swagger-converter-0.2.0.tgz", + "integrity": "sha1-NUAjz8XtPU72iVwxAYkGe75m1hY=", + "requires": { + "URIjs": "^1.16.0" + } }, - "semver": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", - "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==" + "swagger-editor": { + "version": "2.10.5", + "resolved": "https://registry.npmjs.org/swagger-editor/-/swagger-editor-2.10.5.tgz", + "integrity": "sha1-pDFsyw1Ap30w2t+R8PTbfkdflIo=" }, - "send": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", - "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", + "swagger-jsdoc": { + "version": "3.2.9", + "resolved": "https://registry.npmjs.org/swagger-jsdoc/-/swagger-jsdoc-3.2.9.tgz", + "integrity": "sha512-yEGoqnIA5Owb15266x8JyQQM054kyhqkqz/zGxCEsrcx/fq/gf14alEp3qqLpknGOxTr3cqxQr3LrgOxXVm5vg==", "requires": { - "debug": "2.6.9", - "depd": "~1.1.2", - "destroy": "~1.0.4", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "~1.6.2", - "mime": "1.4.1", - "ms": "2.0.0", - "on-finished": "~2.3.0", - "range-parser": "~1.2.0", - "statuses": "~1.4.0" + "commander": "2.17.1", + "doctrine": "2.1.0", + "glob": "7.1.3", + "js-yaml": "3.13.1", + "swagger-parser": "5.0.5" }, "dependencies": { - "statuses": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", - "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" + "commander": { + "version": "2.17.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", + "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==" + }, + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "requires": { + "esutils": "^2.0.2" + } } } }, - "serve-static": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz", - "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==", + "swagger-methods": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/swagger-methods/-/swagger-methods-1.0.8.tgz", + "integrity": "sha512-G6baCwuHA+C5jf4FNOrosE4XlmGsdjbOjdBK4yuiDDj/ro9uR4Srj3OR84oQMT8F3qKp00tYNv0YN730oTHPZA==" + }, + "swagger-parser": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/swagger-parser/-/swagger-parser-5.0.5.tgz", + "integrity": "sha512-6UiaUT9nH5nEzvxDvwZpTfhCs2VOwxrP9neZ83QpsTA3mMGHdun4x8vSXiqjaGQzLh2LG8ND5TRhmVNG1hRUqA==", "requires": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.2", - "send": "0.16.2" + "call-me-maybe": "^1.0.1", + "debug": "^3.1.0", + "json-schema-ref-parser": "^5.1.3", + "ono": "^4.0.6", + "openapi-schema-validation": "^0.4.2", + "swagger-methods": "^1.0.4", + "swagger-schema-official": "2.0.0-bab6bed", + "z-schema": "^3.23.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.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + } } }, - "setprototypeof": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" + "swagger-schema-official": { + "version": "2.0.0-bab6bed", + "resolved": "https://registry.npmjs.org/swagger-schema-official/-/swagger-schema-official-2.0.0-bab6bed.tgz", + "integrity": "sha1-cAcEaNbSl3ylI3suUZyn0Gouo/0=" }, - "sliced": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz", - "integrity": "sha1-CzpmK10Ewxd7GSa+qCsD+Dei70E=" + "swagger-test-templates": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/swagger-test-templates/-/swagger-test-templates-1.5.1.tgz", + "integrity": "sha512-p5EotTsyVunfNGvIr07r33Tij5p4r1aUv7QFvYYW3iO6pEUo2OXxINufkx8jBjD4/4hvP2ZlCjgLDexT2ClnGw==", + "requires": { + "handlebars": "^4.0.5", + "js-string-escape": "^1.0.1", + "json-schema-deref-sync": "^0.3.1", + "lodash": "^3.10.0", + "sanitize-filename": "^1.3.0", + "string": "^3.3.0" + }, + "dependencies": { + "lodash": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", + "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=" + } + } }, - "slug": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/slug/-/slug-0.9.1.tgz", - "integrity": "sha1-rwj2CKfBFRa2F3iqgA3OhMUYz9o=", + "swagger-tools": { + "version": "0.9.16", + "resolved": "https://registry.npmjs.org/swagger-tools/-/swagger-tools-0.9.16.tgz", + "integrity": "sha1-45+uPVgdcTaCSR4ZJs2Hvywgm/s=", "requires": { - "unicode": ">= 0.3.1" + "async": "^1.3.0", + "body-parser": "1.12.4", + "commander": "^2.8.1", + "debug": "^2.2.0", + "js-yaml": "^3.3.1", + "json-refs": "^2.1.5", + "lodash-compat": "^3.10.0", + "multer": "^1.1.0", + "parseurl": "^1.3.0", + "path-to-regexp": "^1.2.0", + "qs": "^4.0.0", + "serve-static": "^1.10.0", + "spark-md5": "^1.0.0", + "string": "^3.3.0", + "superagent": "^1.2.0", + "swagger-converter": "^0.1.7", + "traverse": "^0.6.6", + "z-schema": "^3.15.4" + }, + "dependencies": { + "body-parser": { + "version": "1.12.4", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.12.4.tgz", + "integrity": "sha1-CQcAxLoohiqFIO83g5X97l9hwik=", + "requires": { + "bytes": "1.0.0", + "content-type": "~1.0.1", + "debug": "~2.2.0", + "depd": "~1.0.1", + "iconv-lite": "0.4.8", + "on-finished": "~2.2.1", + "qs": "2.4.2", + "raw-body": "~2.0.1", + "type-is": "~1.6.2" + }, + "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" + } + }, + "qs": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-2.4.2.tgz", + "integrity": "sha1-9854jld33wtQENp/fE5zujJHD1o=" + } + } + }, + "bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-1.0.0.tgz", + "integrity": "sha1-NWnt6Lo0MV+rmcPpLLBMciDeH6g=" + }, + "component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=" + }, + "cookiejar": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.0.6.tgz", + "integrity": "sha1-Cr81atANHFohnYjURRgEbdAmrP4=" + }, + "depd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.0.1.tgz", + "integrity": "sha1-gK7GTJ1tl+ZcwqnKqTwKpqv3Oqo=" + }, + "ee-first": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.0.tgz", + "integrity": "sha1-ag18YiHkkP7v2S7D9EHJzozQl/Q=" + }, + "extend": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.0.tgz", + "integrity": "sha1-WkdDU7nzNT3dgXbf03uRyDpG8dQ=" + }, + "form-data": { + "version": "1.0.0-rc3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-1.0.0-rc3.tgz", + "integrity": "sha1-01vGLn+8KTeuePlIqqDTjZBgdXc=", + "requires": { + "async": "^1.4.0", + "combined-stream": "^1.0.5", + "mime-types": "^2.1.3" + } + }, + "formidable": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.0.16.tgz", + "integrity": "sha1-SRbP38TL7QILJXpqlQWpqzjCzQ4=" + }, + "iconv-lite": { + "version": "0.4.8", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.8.tgz", + "integrity": "sha1-xgGadZXyzvynAuq2lKAQvNkpjSA=" + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "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=" + }, + "on-finished": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.2.1.tgz", + "integrity": "sha1-XIXBzDYpn3gCllP2Z/J7a5nrwCk=", + "requires": { + "ee-first": "1.1.0" + } + }, + "path-to-regexp": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz", + "integrity": "sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=", + "requires": { + "isarray": "0.0.1" + } + }, + "qs": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-4.0.0.tgz", + "integrity": "sha1-wx2bdOwn33XlQ6hseHKO2NRiNgc=" + }, + "raw-body": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.0.2.tgz", + "integrity": "sha1-osL5jIUxzumcY9jSOLfel7tln8o=", + "requires": { + "bytes": "2.1.0", + "iconv-lite": "0.4.8" + }, + "dependencies": { + "bytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-2.1.0.tgz", + "integrity": "sha1-rJPEEOL/ycx89LRks4KJBn9eR7Q=" + } + } + }, + "readable-stream": { + "version": "1.0.27-1", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.27-1.tgz", + "integrity": "sha1-a2eYPCA1fO/QfwFlABoW1xDZEHg=", + "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=" + }, + "superagent": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-1.8.5.tgz", + "integrity": "sha1-HA3cOvMOgOuE68BcshItqP6UC1U=", + "requires": { + "component-emitter": "~1.2.0", + "cookiejar": "2.0.6", + "debug": "2", + "extend": "3.0.0", + "form-data": "1.0.0-rc3", + "formidable": "~1.0.14", + "methods": "~1.1.1", + "mime": "1.3.4", + "qs": "2.3.3", + "readable-stream": "1.0.27-1", + "reduce-component": "1.0.1" + }, + "dependencies": { + "qs": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-2.3.3.tgz", + "integrity": "sha1-6eha2+ddoLvkyOBHaghikPhjtAQ=" + } + } + }, + "swagger-converter": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/swagger-converter/-/swagger-converter-0.1.7.tgz", + "integrity": "sha1-oJdRnG8e5N1n4wjZtT3cnCslf5c=", + "requires": { + "lodash.clonedeep": "^2.4.1" + } + } } }, - "sshpk": { - "version": "1.14.2", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.2.tgz", - "integrity": "sha1-xvxhZIo9nE52T9P8306hBeSSupg=", + "swagger-ui-dist": { + "version": "3.22.2", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-3.22.2.tgz", + "integrity": "sha512-37RuIYRKjFfoLPctzA2dWvBMJjWa1GLc0NgmubuRbr16b3pSuf9S0rY+82l6F86VV4xlgdPiLMcOZhqcIg/OOg==" + }, + "swagger-ui-express": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-4.0.5.tgz", + "integrity": "sha512-zNERqRBrFy2ZcYOosCwirPTEyFbQp/I5AjPE6kKYTgVMNt9szNZQ5mUKgGqS3t/NhH8SWfDHnRCsHKaCo4S4+g==", "requires": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" + "swagger-ui-dist": "^3.18.1" } }, - "statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" + "table": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/table/-/table-5.2.3.tgz", + "integrity": "sha512-N2RsDAMvDLvYwFcwbPyF3VmVSSkuF+G1e+8inhBLtHpvwXGw4QRPEZhihQNeEN0i1up6/f6ObCJXNdlRG3YVyQ==", + "dev": true, + "requires": { + "ajv": "^6.9.1", + "lodash": "^4.17.11", + "slice-ansi": "^2.1.0", + "string-width": "^3.0.0" + }, + "dependencies": { + "ajv": { + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz", + "integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==", + "dev": true, + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "term-size": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/term-size/-/term-size-1.2.0.tgz", + "integrity": "sha1-RYuDiH8oj8Vtb/+/rSYuJmOO+mk=", + "requires": { + "execa": "^0.7.0" + } + }, + "test-exclude": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-5.2.3.tgz", + "integrity": "sha512-M+oxtseCFO3EDtAaGH7iiej3CBkzXqFMbzqYAACdzKui4eZA+pq3tZEwChvOdNfa7xxy8BfbmgJSIr43cC/+2g==", + "dev": true, + "requires": { + "glob": "^7.1.3", + "minimatch": "^3.0.4", + "read-pkg-up": "^4.0.0", + "require-main-filename": "^2.0.0" + }, + "dependencies": { + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", + "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dev": true, + "requires": { + "pify": "^3.0.0" + } + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + }, + "read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", + "dev": true, + "requires": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + } + }, + "read-pkg-up": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-4.0.0.tgz", + "integrity": "sha512-6etQSH7nJGsK0RbG/2TeDzZFa8shjQ1um+SwQQ5cwKy0dhSXdOncEhb1CPpvQG4h7FyOV6EB6YlV0yJvZQNAkA==", + "dev": true, + "requires": { + "find-up": "^3.0.0", + "read-pkg": "^3.0.0" + } + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + } + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" + }, + "timed-out": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", + "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=" + }, + "timers-ext": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.7.tgz", + "integrity": "sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ==", + "requires": { + "es5-ext": "~0.10.46", + "next-tick": "1" + } + }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "requires": { + "os-tmpdir": "~1.0.2" + } + }, + "to-array": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz", + "integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA=" + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=" + }, + "to-iso-string": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/to-iso-string/-/to-iso-string-0.0.2.tgz", + "integrity": "sha1-TcGeZk38y+Jb2NtQiwDG2hWCVdE=" + }, + "to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "requires": { + "kind-of": "^3.0.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" + } + } + } + }, + "to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "requires": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + }, + "toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" + }, + "topo": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/topo/-/topo-3.0.3.tgz", + "integrity": "sha512-IgpPtvD4kjrJ7CRA3ov2FhWQADwv+Tdqbsf1ZnPUSAtCJ9e1Z44MmoSGDXGk4IppoZA7jd/QRkNddlLJWlUZsQ==", + "requires": { + "hoek": "6.x.x" + } + }, + "toposort-class": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toposort-class/-/toposort-class-1.0.1.tgz", + "integrity": "sha1-f/0feMi+KMO6Rc1OGj9e4ZO9mYg=" + }, + "touch": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", + "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", + "requires": { + "nopt": "~1.0.10" + } }, "tough-cookie": { "version": "2.3.4", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz", "integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==", "requires": { - "punycode": "^1.4.1" + "punycode": "^1.4.1" + } + }, + "traverse": { + "version": "0.6.6", + "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.6.tgz", + "integrity": "sha1-y99WD9e5r2MlAv7UD5GMFX6pcTc=" + }, + "tree-kill": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.1.tgz", + "integrity": "sha512-4hjqbObwlh2dLyW4tcz0Ymw0ggoaVDMveUB9w8kFSQScdRLo0gxO9J7WFcUBo+W3C1TLdFIEwNOWebgZZ0RH9Q==" + }, + "trim-newlines": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", + "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=" + }, + "trim-right": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", + "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=" + }, + "true-case-path": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/true-case-path/-/true-case-path-1.0.3.tgz", + "integrity": "sha512-m6s2OdQe5wgpFMC+pAJ+q9djG82O2jcHPOI6RNg1yy9rCYR+WD6Nbpl32fDpfC56nirdRy+opFa/Vk7HYhqaew==", + "requires": { + "glob": "^7.1.2" + } + }, + "truncate-utf8-bytes": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz", + "integrity": "sha1-QFkjkJWS1W94pYGENLC3hInKXys=", + "requires": { + "utf8-byte-length": "^1.0.1" } }, + "tslib": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", + "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==" + }, "tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -1086,6 +10763,21 @@ "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", "optional": true }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2" + } + }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + }, "type-is": { "version": "1.6.16", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", @@ -1095,6 +10787,29 @@ "mime-types": "~2.1.18" } }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" + }, + "uglify-js": { + "version": "3.5.9", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.5.9.tgz", + "integrity": "sha512-WpT0RqsDtAWPNJK955DEnb6xjymR8Fn0OlK4TT4pS0ASYsVPqr5ELhgwOwLCP5J5vHeJ4xmMmz3DEgdqC10JeQ==", + "optional": true, + "requires": { + "commander": "~2.20.0", + "source-map": "~0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "optional": true + } + } + }, "uid-safe": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", @@ -1103,6 +10818,35 @@ "random-bytes": "~1.0.0" } }, + "uid2": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/uid2/-/uid2-0.0.3.tgz", + "integrity": "sha1-SDEm4Rd03y9xuLY53NeZw3YWK4I=" + }, + "umzug": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/umzug/-/umzug-2.2.0.tgz", + "integrity": "sha512-xZLW76ax70pND9bx3wqwb8zqkFGzZIK8dIHD9WdNy/CrNfjWcwQgQkGCuUqcuwEBvUm+g07z+qWvY+pxDmMEEw==", + "requires": { + "babel-runtime": "^6.23.0", + "bluebird": "^3.5.3" + }, + "dependencies": { + "bluebird": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.4.tgz", + "integrity": "sha512-FG+nFEZChJrbQ9tIccIfZJBz3J7mLrAhxakAbnrJWn8d7aKOC+LWifa0G+p4ZqKp4y13T7juYvdhq9NzKdsrjw==" + } + } + }, + "undefsafe": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.2.tgz", + "integrity": "sha1-Il9rngM3Zj4Njnz9aG/Cg2zKznY=", + "requires": { + "debug": "^2.2.0" + } + }, "underscore": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.1.tgz", @@ -1113,11 +10857,191 @@ "resolved": "https://registry.npmjs.org/unicode/-/unicode-11.0.1.tgz", "integrity": "sha512-+cHtykLb+eF1yrSLWTwcYBrqJkTfX7Quoyg7Juhe6uylF43ZbMdxMuSHNYlnyLT8T7POAvavgBthzUF9AIaQvQ==" }, + "unicode-canonical-property-names-ecmascript": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", + "integrity": "sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ==" + }, + "unicode-match-property-ecmascript": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz", + "integrity": "sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg==", + "requires": { + "unicode-canonical-property-names-ecmascript": "^1.0.4", + "unicode-property-aliases-ecmascript": "^1.0.4" + } + }, + "unicode-match-property-value-ecmascript": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.1.0.tgz", + "integrity": "sha512-hDTHvaBk3RmFzvSl0UVrUmC3PuW9wKVnpoUDYH0JDkSIovzw+J5viQmeYHxVSBptubnr7PbH2e0fnpDRQnQl5g==" + }, + "unicode-property-aliases-ecmascript": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.0.5.tgz", + "integrity": "sha512-L5RAqCfXqAwR3RriF8pM0lU0w4Ryf/GgzONwi6KnL1taJQa7x1TCxdJnILX59WIGOwR57IVxn7Nej0fz1Ny6fw==" + }, + "union-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz", + "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=", + "requires": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^0.4.3" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + }, + "set-value": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz", + "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=", + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.1", + "to-object-path": "^0.3.0" + } + } + } + }, + "uniqid": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/uniqid/-/uniqid-5.0.3.tgz", + "integrity": "sha512-R2qx3X/LYWSdGRaluio4dYrPXAJACTqyUjuyXHoJLBUOIfmMcnYOyY2d6Y4clZcIz5lK6ZaI0Zzmm0cPfsIqzQ==" + }, + "unique-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-1.0.0.tgz", + "integrity": "sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo=", + "requires": { + "crypto-random-string": "^1.0.0" + } + }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" + }, "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" }, + "unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "requires": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "dependencies": { + "has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "requires": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "requires": { + "isarray": "1.0.0" + } + } + } + }, + "has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=" + } + } + }, + "unzip-response": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unzip-response/-/unzip-response-2.0.1.tgz", + "integrity": "sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c=" + }, + "upath": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.1.2.tgz", + "integrity": "sha512-kXpym8nmDmlCBr7nKdIx8P2jNBa+pBpIUFRnKJ4dr8htyYGJFokkr2ZvERRtUN+9SY+JqXouNgUPtv6JQva/2Q==" + }, + "update-notifier": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-2.5.0.tgz", + "integrity": "sha512-gwMdhgJHGuj/+wHJJs9e6PcCszpxR1b236igrOkUofGhqJuG+amlIKwApH1IW1WWl7ovZxsX49lMBWLxSdm5Dw==", + "requires": { + "boxen": "^1.2.1", + "chalk": "^2.0.1", + "configstore": "^3.0.0", + "import-lazy": "^2.1.0", + "is-ci": "^1.0.10", + "is-installed-globally": "^0.1.0", + "is-npm": "^1.0.0", + "latest-version": "^3.0.0", + "semver-diff": "^2.0.0", + "xdg-basedir": "^3.0.0" + } + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "requires": { + "punycode": "^2.1.0" + }, + "dependencies": { + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + } + } + }, + "urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=" + }, + "url-parse-lax": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", + "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=", + "requires": { + "prepend-http": "^1.0.1" + } + }, + "use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==" + }, + "utf8-byte-length": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.4.tgz", + "integrity": "sha1-9F8VDExm7uloGGUFq5P8u4rWv2E=" + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, "utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", @@ -1128,6 +11052,33 @@ "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" }, + "v8flags": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.1.3.tgz", + "integrity": "sha512-amh9CCg3ZxkzQ48Mhcb8iX7xpAfYJgePHxWMQCBWECpOSqJUXgY26ncA61UTV0BkPqfhcy6mzwCIoP4ygxpW8w==", + "requires": { + "homedir-polyfill": "^1.0.1" + } + }, + "valid-url": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/valid-url/-/valid-url-1.0.9.tgz", + "integrity": "sha1-HBRHm0DxOXp1eC8RXkCGRHQzogA=" + }, + "validate-npm-package-license": { + "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==", + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "validator": { + "version": "10.11.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-10.11.0.tgz", + "integrity": "sha512-X/p3UZerAIsbBfN/IwahhYaBbY68EN/UQBWHtsbXGT5bfrH/p4NQzUCG1kF/rtKaNpnJ7jAu6NGTdSNtyNIXMw==" + }, "vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -1142,6 +11093,476 @@ "core-util-is": "1.0.2", "extsprintf": "^1.2.0" } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", + "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=" + }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "requires": { + "string-width": "^1.0.2 || 2" + } + }, + "widest-line": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-2.0.1.tgz", + "integrity": "sha512-Ba5m9/Fa4Xt9eb2ELXt77JxVDV8w7qQrH0zS/TWSJdLyAwQjWoOzpzj5lwVftDz6n/EOu3tNACS84v509qwnJA==", + "requires": { + "string-width": "^2.1.1" + } + }, + "wkx": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/wkx/-/wkx-0.4.6.tgz", + "integrity": "sha512-LHxXlzRCYQXA9ZHgs8r7Gafh0gVOE8o3QmudM1PIkOdkXXjW7Thcl+gb2P2dRuKgW8cqkitCRZkkjtmWzpHi7A==", + "requires": { + "@types/node": "*" + } + }, + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "dev": true + }, + "wrap-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "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=", + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "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" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "write": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", + "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", + "dev": true, + "requires": { + "mkdirp": "^0.5.1" + } + }, + "write-file-atomic": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.2.tgz", + "integrity": "sha512-s0b6vB3xIVRLWywa6X9TOMA7k9zio0TMOsl9ZnDkliA/cfJlpHXAscj0gbHVJiTdIuAYpIyqS5GW91fqm6gG5g==", + "requires": { + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.2" + } + }, + "ws": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.1.4.tgz", + "integrity": "sha512-eqZfL+NE/YQc1/ZynhojeV8q+H050oR8AZ2uIev7RU10svA9ZnJUddHcOUZTJLinZ9yEfdA2kSATS2qZK5fhJA==", + "requires": { + "async-limiter": "~1.0.0" + } + }, + "xdg-basedir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-3.0.0.tgz", + "integrity": "sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ=" + }, + "xmldom": { + "version": "0.1.27", + "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.1.27.tgz", + "integrity": "sha1-1QH5ezvbQDr4757MIFcxh6rawOk=" + }, + "xmlhttprequest-ssl": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz", + "integrity": "sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4=" + }, + "xtend": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" + }, + "xtraverse": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/xtraverse/-/xtraverse-0.1.0.tgz", + "integrity": "sha1-t0G60BjveNip0ug63gB7P3lZxzI=", + "requires": { + "xmldom": "0.1.x" + } + }, + "y18n": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", + "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=" + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" + }, + "yargs": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.0.tgz", + "integrity": "sha1-a6MY6xaWFyf10oT46gA+jWFU0Mg=", + "requires": { + "camelcase": "^3.0.0", + "cliui": "^3.2.0", + "decamelize": "^1.1.1", + "get-caller-file": "^1.0.1", + "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.2", + "which-module": "^1.0.0", + "y18n": "^3.2.1", + "yargs-parser": "^5.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=" + }, + "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" + } + }, + "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" + } + }, + "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" + } + }, + "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" + } + }, + "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" + } + }, + "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" + } + } + } + }, + "yargs-parser": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.0.tgz", + "integrity": "sha1-J17PDX/+Bcd+ZOfIbkzZS/DhIoo=", + "requires": { + "camelcase": "^3.0.0" + }, + "dependencies": { + "camelcase": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", + "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=" + } + } + }, + "yargs-unparser": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.5.0.tgz", + "integrity": "sha512-HK25qidFTCVuj/D1VfNiEndpLIeJN78aqgR23nL3y4N0U/91cOAzqfHlF8n2BvoNDcZmJKin3ddNSvOxSr8flw==", + "dev": true, + "requires": { + "flat": "^4.1.0", + "lodash": "^4.17.11", + "yargs": "^12.0.5" + }, + "dependencies": { + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "cliui": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", + "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", + "dev": true, + "requires": { + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", + "wrap-ansi": "^2.0.0" + } + }, + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "invert-kv": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", + "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", + "dev": true + }, + "lcid": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", + "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", + "dev": true, + "requires": { + "invert-kv": "^2.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "os-locale": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", + "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", + "dev": true, + "requires": { + "execa": "^1.0.0", + "lcid": "^2.0.0", + "mem": "^4.0.0" + } + }, + "p-limit": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", + "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "yargs": { + "version": "12.0.5", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", + "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", + "dev": true, + "requires": { + "cliui": "^4.0.0", + "decamelize": "^1.2.0", + "find-up": "^3.0.0", + "get-caller-file": "^1.0.1", + "os-locale": "^3.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1 || ^4.0.0", + "yargs-parser": "^11.1.1" + } + }, + "yargs-parser": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", + "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + }, + "yeast": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", + "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=" + }, + "z-schema": { + "version": "3.25.1", + "resolved": "https://registry.npmjs.org/z-schema/-/z-schema-3.25.1.tgz", + "integrity": "sha512-7tDlwhrBG+oYFdXNOjILSurpfQyuVgkRe3hB2q8TEssamDHB7BbLWYkYO98nTn0FibfdFroFKDjndbgufAgS/Q==", + "requires": { + "commander": "^2.7.1", + "core-js": "^2.5.7", + "lodash.get": "^4.0.0", + "lodash.isequal": "^4.0.0", + "validator": "^10.0.0" + } } } } diff --git a/package.json b/package.json index 70470658..db23e318 100644 --- a/package.json +++ b/package.json @@ -2,31 +2,93 @@ "name": "express-authorshaven", "version": "1.0.0", "description": "A Social platform for the creative at heart", - "main": "index.js", + "main": "src/index.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "test": "NODE_ENV=test sequelize db:migrate:undo:all && npm run db:migrate:test && npm run db:migrate:seed:test && NODE_ENV=test nyc mocha --require @babel/register --timeout 30000 src/tests/index.js", + "cover": "npm test && nyc report --reporter=text-lcov | coveralls", + "coveralls": "cat coverage/lcov.info | coveralls", + "start": "concurrently \"npm run db:migrate && npm run db:seed\" \"babel-node src/index.js\"", + "dev": "concurrently \"npm run db:seed\" \"nodemon src/index.js --exec babel-node --presets @babel/preset-env\"", + "scss": "npm-sass templates/sass/style.scss templates/css/style.css", + "db:migrate": "./node_modules/.bin/sequelize db:migrate", + "db:seed": "./node_modules/.bin/sequelize db:seed:all", + "db:migrate:undo:all": "./node_modules/.bin/sequelize db:migrate:undo:all", + "db:migrate:test": "NODE_ENV=test ./node_modules/.bin/sequelize db:migrate", + "db:migrate:seed:test": "NODE_ENV=test ./node_modules/.bin/sequelize db:seed:all", + "db:migrate:undo:all:test": "NODE_ENV=test ./node_modules/.bin/sequelize db:migrate:undo:all" }, "author": "Andela Simulations Programme", "license": "MIT", "dependencies": { - "body-parser": "^1.18.3", - "cors": "^2.8.4", - "dotenv": "^6.0.0", + "@babel/cli": "^7.4.4", + "@babel/core": "^7.4.5", + "@babel/node": "^7.4.5", + "@babel/preset-env": "^7.4.5", + "@babel/register": "^7.4.4", + "@hapi/joi": "^15.0.3", + "@sendgrid/mail": "^6.4.0", + "bcryptjs": "^2.4.3", + "body-parser": "^1.19.0", + "chance": "^1.0.18", + "cheke": "^1.0.3", + "cloudinary": "^1.14.0", + "concurrently": "^4.1.0", + "cors": "^2.8.5", + "datauri": "^2.0.0", + "dotenv": "^6.2.0", "ejs": "^2.6.1", - "errorhandler": "^1.5.0", - "express": "^4.16.3", + "errorhandler": "^1.5.1", + "express": "^4.17.1", "express-jwt": "^5.3.1", - "express-session": "^1.15.6", - "jsonwebtoken": "^8.3.0", + "express-session": "^1.16.1", + "extend": "^3.0.2", + "http-errors": "^1.7.2", + "joi": "^14.3.1", + "jsonwebtoken": "^8.5.1", + "lodash": "^4.17.11", "method-override": "^2.3.10", "methods": "^1.1.2", - "mongoose": "^5.2.2", - "mongoose-unique-validator": "^2.0.1", - "morgan": "^1.9.0", + "morgan": "^1.9.1", + "mpath": "^0.5.2", + "multer": "^1.4.1", + "multer-storage-cloudinary": "^2.2.1", + "npm-sass": "^2.3.0", + "open": "^6.3.0", "passport": "^0.4.0", + "passport-facebook": "^3.0.0", + "passport-google": "^0.3.0", + "passport-google-oauth": "^2.0.0", + "passport-google-plus": "^0.5.1", + "passport-jwt": "^4.0.0", "passport-local": "^1.0.0", - "request": "^2.87.0", - "slug": "^0.9.1", - "underscore": "^1.9.1" + "passport-twitter": "^1.0.4", + "pg": "^7.11.0", + "pg-hstore": "^2.3.2", + "rosie": "^2.0.1", + "sequelize": "^5.8.7", + "sequelize-cli": "^5.4.0", + "sequelize-easy-query": "^1.1.0", + "sequelize-test-helpers": "^1.1.1", + "slug": "^1.1.0", + "socket.io": "^2.2.0", + "swagger": "^0.7.5", + "swagger-jsdoc": "^3.2.9", + "swagger-ui-express": "^4.0.5", + "underscore": "^1.9.1", + "uniqid": "^5.0.3" + }, + "devDependencies": { + "chai": "^4.2.0", + "chai-http": "^4.3.0", + "coveralls": "^3.0.3", + "eslint": "^5.16.0", + "eslint-config-airbnb-base": "^13.1.0", + "eslint-plugin-import": "^2.17.3", + "mocha": "^6.1.4", + "mocha-lcov-reporter": "^1.3.0", + "nodemon": "^1.19.1", + "nyc": "^14.1.1", + "sinon": "^7.3.2", + "sinon-chai": "^3.3.0" } } diff --git a/routes/api/index.js b/routes/api/index.js deleted file mode 100644 index 2a39c402..00000000 --- a/routes/api/index.js +++ /dev/null @@ -1,18 +0,0 @@ -const router = require("express").Router(); - -router.use("/", require("./users")); - -router.use(function(err, req, res, next) { - if (err.name === "ValidationError") { - return res.status(422).json({ - errors: Object.keys(err.errors).reduce(function(errors, key) { - errors[key] = err.errors[key].message; - return errors; - }, {}) - }); - } - - return next(err); -}); - -module.exports = router; diff --git a/routes/api/users.js b/routes/api/users.js deleted file mode 100644 index c8cd3363..00000000 --- a/routes/api/users.js +++ /dev/null @@ -1,87 +0,0 @@ -const mongoose = require("mongoose"); -const router = require("express").Router(); -const passport = require("passport"); -const User = mongoose.model("User"); - -router.get("/user", function(req, res, next) { - User.findById(req.payload.id) - .then(function(user) { - if (!user) { - return res.sendStatus(401); - } - return res.json({ user: user.toAuthJSON() }); - }) - .catch(next); -}); - -router.put("/user", function(req, res, next) { - User.findById(req.payload.id) - .then(function(user) { - if (!user) { - return res.sendStatus(401); - } - - // only update fields that were actually passed... - if (typeof req.body.user.username !== "undefined") { - user.username = req.body.user.username; - } - if (typeof req.body.user.email !== "undefined") { - user.email = req.body.user.email; - } - if (typeof req.body.user.bio !== "undefined") { - user.bio = req.body.user.bio; - } - if (typeof req.body.user.image !== "undefined") { - user.image = req.body.user.image; - } - if (typeof req.body.user.password !== "undefined") { - user.setPassword(req.body.user.password); - } - - return user.save().then(function() { - return res.json({ user: user.toAuthJSON() }); - }); - }) - .catch(next); -}); - -router.post("/users/login", function(req, res, next) { - if (!req.body.user.email) { - return res.status(422).json({ errors: { email: "can't be blank" } }); - } - - if (!req.body.user.password) { - return res.status(422).json({ errors: { password: "can't be blank" } }); - } - passport.authenticate("local", { session: false }, function( - err, - user, - info - ) { - if (err) { - return next(err); - } - - if (user) { - return res.json({ user: user.toAuthJSON() }); - } else { - return res.status(422).json(info); - } - })(req, res, next); -}); - -router.post("/users", function(req, res, next) { - const user = new User(); - - user.username = req.body.user.username; - user.email = req.body.user.email; - user.setPassword(req.body.user.password); - - user.save() - .then(function() { - return res.json({ user: user.toAuthJSON() }); - }) - .catch(next); -}); - -module.exports = router; diff --git a/routes/index.js b/routes/index.js deleted file mode 100644 index 1ea5ab2d..00000000 --- a/routes/index.js +++ /dev/null @@ -1,5 +0,0 @@ -const router = require("express").Router(); - -router.use("/api", require("./api")); - -module.exports = router; diff --git a/src/app.js b/src/app.js new file mode 100644 index 00000000..e45bec1b --- /dev/null +++ b/src/app.js @@ -0,0 +1,73 @@ +import http from 'http'; +import path from 'path'; +import createError from 'http-errors'; +import swaggerUi from 'swagger-ui-express'; +import express from 'express'; +import logger from 'morgan'; +import dotenv from 'dotenv'; +import cors from 'cors'; +import session from 'express-session'; +import passport from 'passport'; +import socketIo from 'socket.io'; +import routes from './routes'; +import * as swaggerDocument from '../swagger.json'; +import './helpers/eventListener'; + +const app = express(); +const server = http.createServer(app); +const io = socketIo(server); + +dotenv.config(); + +app.use( + session({ + secret: process.env.SECRET_KEY || 'authorshaven', + cookie: { maxAge: 60000 }, + resave: true, + saveUninitialized: true + }) +); + +// swagger route + +app.use(logger('dev')); + +app.use(express.json()); +app.use(express.urlencoded({ extended: false })); +app.use(cors()); +app.use(passport.initialize()); +app.use(passport.session()); + +app.use((req, res, next) => { + req.io = io; + next(); +}); + +app.use(express.static(path.join(__dirname, '../templates'))); +app.use('/mockups', express.static(path.join(__dirname, '../templates/html'))); + +app.use('/api/v1/documentation', swaggerUi.serve, swaggerUi.setup(swaggerDocument)); +app.use('/api/v1/', routes); + +// catch 404 and forward to error handler +app.use((req, res, next) => { + next(createError(404)); +}); + +// error handler +app.use((err, req, res, next) => { + // set locals, only providing error in development + res.locals.message = err.message; + res.locals.error = req.app.get('env') === 'development' ? err : {}; + + // render the error page + res.status(err.status || 500); + res.send({ + message: err.message, + error: err.status + }); + next(); +}); + +app.server = server; +export default app; diff --git a/src/config/cloudinaryConfig.js b/src/config/cloudinaryConfig.js new file mode 100644 index 00000000..d5e3375b --- /dev/null +++ b/src/config/cloudinaryConfig.js @@ -0,0 +1,10 @@ +import { config } from 'cloudinary'; +import dotenv from 'dotenv'; + +dotenv.config(); +const cloudinaryConfig = () => config({ + cloud_name: process.env.CLOUDINARY_CLOUD_NAME || null, + api_key: process.env.CLOUDINARY_API_KEY || null, + api_secret: process.env.CLOUDINARY_API_SECRET || null +}); +export default cloudinaryConfig; diff --git a/src/config/dbConfig.js b/src/config/dbConfig.js new file mode 100644 index 00000000..259201f6 --- /dev/null +++ b/src/config/dbConfig.js @@ -0,0 +1,35 @@ +import dotenv from 'dotenv'; + +dotenv.config(); + +const config = { + development: { + use_env_variable: 'DATABASE_URL_DEV', + username: process.env.DB_USER_DEV, + password: process.env.DB_PASSWORD_DEV, + database: process.env.DB_NAME_DEV, + host: process.env.DB_HOST_DEV, + dialect: 'postgres', + seederStorage: 'sequelize' + }, + test: { + use_env_variable: 'DATABASE_URL_TEST', + username: process.env.DB_USER_TEST, + password: process.env.DB_PASSWORD_TEST, + database: process.env.DB_NAME_TEST, + host: process.env.DB_HOST_TEST, + dialect: 'postgres', + seederStorage: 'sequelize' + }, + production: { + use_env_variable: 'DATABASE_URL', + username: process.env.DB_USER, + password: process.env.DB_PASSWORD, + database: process.env.DB_NAME, + host: process.env.DB_HOST, + dialect: 'postgres', + seederStorage: 'sequelize' + } +}; + +module.exports = config; diff --git a/src/config/notificationConfig.sample.js b/src/config/notificationConfig.sample.js new file mode 100644 index 00000000..a2004c93 --- /dev/null +++ b/src/config/notificationConfig.sample.js @@ -0,0 +1,14 @@ +export default { + inApp: { + articles: { + show: true, + on: ['publish', 'comment', 'like'] + } + }, + email: { + articles: { + show: true, + on: ['publish', 'comment'] + } + } +}; diff --git a/src/config/passportConfig.js b/src/config/passportConfig.js new file mode 100644 index 00000000..9b8e3ed3 --- /dev/null +++ b/src/config/passportConfig.js @@ -0,0 +1,35 @@ +import dotenv from 'dotenv'; + +dotenv.config(); + +export default { + serializeUser: (user, done) => done(null, user), + deserializeUser: (obj, done) => done(null, obj), + facebook: { + clientID: process.env.FACEBOOK_APP_ID, + clientSecret: process.env.FACEBOOK_APP_SECRET, + callbackURL: `${process.env.APP_URL_BACKEND}/api/v1/auth/facebook/callback`, + profileFields: ['id', 'name', 'photos', 'email'], + callbackFunc: (accessToken, refreshToken, profile, cb) => cb(null, profile) + }, + twitter: { + consumerKey: process.env.TWITTER_CONSUMER_KEY, + consumerSecret: process.env.TWITTER_CONSUMER_SECRET, + callbackURL: `${process.env.APP_URL_BACKEND}/api/v1/auth/twitter/callback`, + profileFields: ['id', 'name', 'photos', 'email'], + includeEmail: true, + callbackFunc: (token, tokenSecret, profile, cb) => cb(null, profile) + }, + google: { + consumerKey: process.env.GOOGLE_CONSUMER_KEY, + consumerSecret: process.env.GOOGLE_CONSUMER_SECRET, + callbackURL: `${process.env.APP_URL_BACKEND}/api/v1/auth/google/callback`, + callbackFunc: (accessToken, refreshToken, profile, cb) => cb(null, profile) + }, + google2: { + clientID: process.env.GOOGLE_CONSUMER_KEY, + clientSecret: process.env.GOOGLE_CONSUMER_SECRET, + callbackURL: `${process.env.APP_URL_BACKEND}/api/v1/auth/google/callback`, + callbackFunc: (accessToken, refreshToken, profile, cb) => cb(null, profile) + } +}; diff --git a/src/config/passportLocalConfig.js b/src/config/passportLocalConfig.js new file mode 100644 index 00000000..48ca962e --- /dev/null +++ b/src/config/passportLocalConfig.js @@ -0,0 +1,13 @@ + +import models from '../models'; + +const { User } = models; +const passport = async (jwtPayload, done) => { + const user = await User.findOne({ where: { id: jwtPayload.id } }); + if (!user) { + return done(null, false); + } + return done(null, user); +}; + +export default passport; diff --git a/src/config/permissions.js b/src/config/permissions.js new file mode 100644 index 00000000..c4fa65de --- /dev/null +++ b/src/config/permissions.js @@ -0,0 +1,21 @@ +import getAll from '../queries/permissions/findAllPermission'; + +/** + * @returns {array} an array containing the list of permissions + */ +export default async () => { + const allPermissions = await getAll(); + let normal = {}; + let admin = {}; + allPermissions.forEach((perm) => { + if (perm.userType === 'normal') { + normal = perm.permissions; + } else if (perm.userType === 'admin') { + admin = perm.permissions; + } + }); + return { + normal, + admin + }; +}; diff --git a/src/config/status.js b/src/config/status.js new file mode 100644 index 00000000..eb7f00b1 --- /dev/null +++ b/src/config/status.js @@ -0,0 +1,14 @@ +export default { + OK: 200, + CREATED: 201, + NO_CONTENT: 204, + BAD_REQUEST: 400, + UNAUTHORIZED: 401, + ACCESS_DENIED: 403, + NOT_FOUND: 404, + TIMEOUT: 408, + EXIST: 409, + SERVER_ERROR: 500, + NOT_MODIFIED: 304, + REDIRECT: 302 +}; diff --git a/src/controllers/ArticleController.js b/src/controllers/ArticleController.js new file mode 100644 index 00000000..58025f2b --- /dev/null +++ b/src/controllers/ArticleController.js @@ -0,0 +1,222 @@ +import status from '../config/status'; +import * as helpers from '../helpers'; +import { Article } from '../queries'; +/** + * A class to handle actions performed on articles + */ +export default class ArticleController { + /** + * @param {object} req Request sent to the route + * @param {object} res Response from server + * @returns {object} Object representing the response returned + */ + static async saveArticle(req, res) { + const { tagList } = req.body; + const coverUrl = req.files && req.files[0] !== undefined && req.files[0].originalname + ? `v${req.files[0].version}/${req.files[0].public_id}.${req.files[0].format}` + : null; + const newArticle = await Article.create({ + userId: req.user.id, + slug: helpers.generator.slug(req.body.title), + title: req.body.title.trim(), + description: req.body.description.trim(), + body: req.body.body.trim(), + coverUrl, + tagList, + readTime: helpers.generator.readtime(req.body.body), + likes: 0, + dislikes: 0 + }); + return res.status(status.CREATED).send({ + article: newArticle + }); + } + + /** + * @param {object} req Request sent to the route + * @param {object} res Response from server + * @returns {object} Object representing the response returned + */ + static async getAllArticles(req, res) { + const { + limit, offset, keyword, author, tag + } = req.query; + const articles = await Article.getAll(parseInt(limit, 0) || 20, offset || 0, { + keyword, + author, + tag + }); + if (articles.length >= 1 && !!articles) { + return res.status(status.OK).send({ + articles, + articlesCount: await helpers.articles.counter('published') + }); + } + return res.status(status.NOT_FOUND).send({ message: 'No articles found' }); + } + + /** + * @param {object} req Request sent to the route + * @param {object} res Response from server + * @returns {object} Object representing the response returned + */ + static async userArticleDrafts(req, res) { + const { limit, offset } = req.query; + const { id } = req.user; + const drafts = await Article.getUserArticles(parseInt(limit, 0) || 20, offset || 0, { + userId: id, + status: 'draft' + }); + if (Object.keys(drafts).length > 0) { + res.status(status.OK).send({ + articles: drafts, + articlesCount: drafts.length + }); + } else { + res.status(status.NOT_FOUND).send({ message: 'No articles found' }); + } + } + + /** + * @param {object} req Request sent to the route + * @param {object} res Response from server + * @returns {object} Object representing the response returned + */ + static async userArticlePublished(req, res) { + const { limit, offset } = req.query; + const published = await Article.getUserArticles(parseInt(limit, 0) || 20, offset || 0, { + userId: req.user.id, + status: 'published' + }); + return published.length >= 1 && !!published + ? res.status(status.OK).send({ + articles: published, + articlesCount: published.length + }) + : res.status(status.NOT_FOUND).send({ message: 'No articles found' }); + } + + /** + * @param {object} req Request sent to the route + * @param {object} res Response from server + * @returns {object} Object representing the response returned + */ + static async getSpecificArticle(req, res) { + const oneArticle = await Article.get({ slug: req.params.slug }); + return res.status(status.OK).send({ + article: oneArticle + }); + } + + /** + * @param {object} req Request sent to the route + * @param {object} res Response from server + * @returns {object} Object representing the response returned + */ + static async update(req, res) { + let updateArticle = {}; + let message = ''; + if (Object.keys(req.body).length > 0) { + updateArticle = req.body; + message = 'Article has been updated'; + } else if (req.url.search(/\/publish/g) > 0) { + [updateArticle, message] = [{ status: 'published' }, 'Article has been published']; + } else if (req.url.search(/\/unpublish/g) > 0) { + [updateArticle, message] = [{ status: 'draft' }, 'Article has been unpublished']; + } else if (req.method === 'DELETE') { + [updateArticle, message] = [{ status: 'deleted' }, 'Article has been deleted']; + } + const updatedArticle = await Article.update(updateArticle, req.params.slug); + return res + .status(status.OK) + .send({ message, article: req.method === 'DELETE' ? null : updatedArticle }); + } + + /** + * @param {object} req Request sent to the route + * @param {object} res Response from server + * @returns {object} Object representing the response returned + */ + static async bookmarkOrFavorite(req, res) { + const resourceAction = req.url.search(/\/bookmark/g) > 0 ? 'bookmark' : 'favorite'; + const result = await Article[resourceAction].add( + req.user.id, + req.params.slug, + req.article.favoritesCount + ); + + if (result.errors) { + switch (result.errors.name) { + case 'SequelizeUniqueConstraintError': + return res.status(status.EXIST).json({ + errors: { [resourceAction]: `this article is already in ${resourceAction}s` } + }); + case 'SequelizeForeignKeyConstraintError': + return res.status(status.UNAUTHORIZED).json({ + errors: { account: 'sorry, your account is not valid' } + }); + default: + return res.status(status.SERVER_ERROR).json({ errors: 'oops, something went wrong' }); + } + } + return res.status(status.CREATED).json({ [resourceAction]: result }); + } + + /** + * @param {object} req Request sent to the route + * @param {object} res Response from server + * @returns {object} Object representing the response returned + */ + static async getBookmarksOrFavorites(req, res) { + const resourceAction = req.url.search(/\/bookmarked/g) > 0 ? 'bookmark' : 'favorite'; + const result = await Article[resourceAction].getAll(req.user.id); + + return result.errors + ? res.status(status.SERVER_ERROR).json({ errors: 'oops, something went wrong' }) + : res.status(status.OK).json({ + [[`${resourceAction}s`]]: result.map((value) => { + delete value.get().favoritedBy; + return value; + }) + }); + } + + /** + * @param {object} req Request sent to the route + * @param {object} res Response from server + * @returns {object} Object representing the response returned + */ + static async removeBookmarkOrFavorite(req, res) { + const resourceAction = req.url.search(/\/bookmark/g) > 0 ? 'bookmark' : 'favorite'; + const [slug, favoritesCount] = [req.params.slug, req.article.favoritesCount]; + const result = await Article[resourceAction].remove(req.user.id, slug, favoritesCount); + + if (result.errors) { + return res.status(status.SERVER_ERROR).json({ + errors: 'oops, something went wrong' + }); + } + + return !result + ? res.status(status.BAD_REQUEST).json({ + errors: { [resourceAction]: `article not removed from ${resourceAction}s` } + }) + : res.status(status.OK).json({ + slug, + userId: req.user.id, + message: `article successfully removed from ${resourceAction}s ` + }); + } + + /** + * @param {object} req Request sent to the route + * @param {object} res Response from server + * @returns {object} Object representing the response returned + */ + static async share(req, res) { + return res.status(status.OK).json({ + message: 'Thank you for sharing!', + article: req.article + }); + } +} diff --git a/src/controllers/ArticleLikeController.js b/src/controllers/ArticleLikeController.js new file mode 100644 index 00000000..a1f0f79c --- /dev/null +++ b/src/controllers/ArticleLikeController.js @@ -0,0 +1,50 @@ +import statusCode from '../config/status'; +import * as article from '../queries/articles/likes'; +import updateArticleLikes from '../queries/articles/updateArticleLikes'; + +/** + * A class to control the Article likes + */ +export default class ArticleLikeController { + /** + * A method to create an article like + * @param {object} req the request to get like + * @param {object} res the response of like from server + * @returns {object} the return after liking a particular article + */ + static async createLike(req, res) { + let message = 'You liked the article'; + const userId = req.user.id; + const { articleSlug, status } = req.params; + const createLike = await article.create({ + userId, + articleSlug, + status + }); + + await updateArticleLikes(req); + if (req.params.status === 'dislike') { + message = 'You disliked the article'; + return res.status(statusCode.CREATED).json({ message, createLike }); + } + return res.status(statusCode.CREATED).json({ message, createLike }); + } + + /** + * A method to display all likes on an article + * @param {object} req the request to get like + * @param {object} res the response of like from server + * @returns {object} the return after liking a particular article + */ + static async getAllLikes(req, res) { + const { articleSlug } = req.params; + const findAllLikes = await article.getAllLikes({ status: 'like', articleSlug }); + const findAllDislikes = await article.getAllLikes({ status: 'dislike', articleSlug }); + return res.status(statusCode.OK).json({ + likes: findAllLikes.length, + dislikes: findAllDislikes.length, + whoLiked: { userId: findAllLikes.map(value => value.userId) }, + whoDisliked: { userId: findAllDislikes.map(value => value.userId) } + }); + } +} diff --git a/src/controllers/AuthLocalController.js b/src/controllers/AuthLocalController.js new file mode 100644 index 00000000..b812c438 --- /dev/null +++ b/src/controllers/AuthLocalController.js @@ -0,0 +1,189 @@ +import 'dotenv/config'; +import { User } from '../queries'; +import * as helper from '../helpers'; +import * as validate from '../helpers/validation'; +import status from '../config/status'; + +const { CI } = process.env; +const { appUrl, travis } = helper.urlHelper.frontend; + +/** + * A class to handle user local authentication + */ +export default class AuthLocalController { + /** + * @description user signup function + * @param {object} req request from user + * @param {object} res response from server + * @return {object} user information & token + */ + static async signup(req, res) { + const { email, firstName, lastName } = req.body; + req.body.password = helper.password.hash(req.body.password); + const newUser = await User.create(req.body); + const errors = newUser.errors ? helper.checkCreateUpdateUserErrors(newUser.errors) : null; + + return errors + ? res.status(errors.code).json({ errors: errors.errors }) + : (await helper.sendMail(email, 'signup', { email, firstName, lastName })) + && res.status(status.CREATED).json({ + message: 'check your email to activate your account', + user: newUser + }); + } + + /** + * @description - login user function + * @param {object} req user request + * @param {object} res response form server + * @returns {object} user token + */ + static async login(req, res) { + const { email, password } = req.body; + const checkUser = await User.findOne({ email }); + if (Object.keys(checkUser).length > 0) { + const comparePassword = helper.password.compare(password, checkUser.password || ''); + if (!comparePassword) { + return res.status(status.UNAUTHORIZED).json({ + errors: { credentials: 'The credentials you provided are incorrect' } + }); + } + const payload = { + id: checkUser.id, + role: checkUser.role, + permissions: checkUser.permissions + }; + const token = helper.token.generate(payload); + delete checkUser.password; + return res.status(status.OK).json({ + message: 'signIn successfully', + user: checkUser, + token + }); + } + } + + /** + * @description function to delete user + * @param {object} req user request object + * @param {object} res response object from server + * @returns {object} return true if user deleted or false when user not deleted + */ + static async deactivateAccount(req, res) { + const { id } = req.params; + const deactivateAccount = await User.update({ isActive: false }, { id }); + return deactivateAccount + ? res.status(status.OK).json({ message: 'User account deleted successfully', userId: id }) + : res.status(status.UNAUTHORIZED).json({ errors: 'Unauthorized access' }); + } + + /** + * @description method to find one user + * @param {object} req user request object + * @param {object} res response object from server + * @returns {object} return all users + */ + static async getOne(req, res) { + const id = Number.parseInt(req.params.id, 10); + const fetchUser = await User.findOne({ id: Number.isNaN(id) ? 0 : id }); + delete fetchUser.password; + return Object.keys(fetchUser).length + ? res.status(status.OK).json({ user: fetchUser }) + : res + .status(status.NOT_FOUND) + .json({ errors: { user: `sorry, user with id "${req.params.id}" not found!!` } }); + } + + /** + * @description function for admin to create user + * @param {object} req user request object + * @param {object} res response object from server + * @returns {object} return true if user created or flase when was not + */ + static async create(req, res) { + const { email, firstName, lastName } = req.body; + req.body.password = helper.password.hash(req.body.password); + const newUser = await User.create(req.body); + if (newUser.errors) { + const errors = helper.checkCreateUpdateUserErrors(newUser.errors); + const { code } = errors; + delete errors.code; + return res.status(code).json(errors); + } + if (newUser) { + await helper.sendMail(email, 'signup', { email, firstName, lastName }); + return res.status(status.CREATED).json({ + message: `activation message sent to ${req.body.email}` + }); + } + } + + /** + * @description function to activate user account + * @param {object} req + * @param {object} res + * @returns {object} it return true if account activeted otherwise it return false + */ + static async activate(req, res) { + const { user } = req; + await User.update({ isActive: true }, { email: user.email }); + return res.redirect(`${(CI && travis) || appUrl}/login`); + } + + /** + * @param {object} req + * @param {object} res + * @return {object} return an object containing the confirmation message + */ + static async sendEmail(req, res) { + const { email } = req.body; + const result = await User.findOne({ email }); // check if the email exist + if (Object.keys(result).length <= 0) { + return res.status(status.NOT_FOUND).json({ + errors: 'email not found..' + }); + } + + await helper.sendMail(email, 'resetPassword', { + email, + names: `${result.firstName} ${result.lastName}` + }); // send mail + + return res.status(status.OK).json({ + message: 'Email sent, please check your email' + }); + } + + /** + * @param {object} req + * @param {object} res + * @return {object} return an object containing the confirmation message + */ + static async updatePassword(req, res) { + const token = req.body.token || req.params.token; + const { passwordOne, passwordTwo } = req.body; + + if (passwordOne !== passwordTwo) { + return res.status(status.BAD_REQUEST).json({ errors: 'Passwords are not matching' }); + } + + if (!req.body.passwordOne || !req.body.passwordTwo) { + return res.status(status.BAD_REQUEST).json({ errors: 'the password can not be empty' }); + } + + const isPasswordValid = validate.password(passwordOne, 'required'); + const isPasswordValidTwo = validate.password(passwordTwo, 'required'); + + if (isPasswordValid.length || isPasswordValidTwo.length) { + return res.status(status.BAD_REQUEST).json({ message: isPasswordValid[0] }); + } + const { email } = helper.token.decode(token); + const isUpdated = await User.update({ password: helper.password.hash(passwordOne) }, { email }); + delete isUpdated.password; + return isUpdated + ? res + .status(status.OK) + .json({ isUpdated, message: 'Success! your password has been changed.' }) + : res.status(status.NOT_MODIFIED).json({ errors: 'Password not updated' }); + } +} diff --git a/src/controllers/AuthPassportController.js b/src/controllers/AuthPassportController.js new file mode 100644 index 00000000..38a01e1f --- /dev/null +++ b/src/controllers/AuthPassportController.js @@ -0,0 +1,90 @@ +import 'dotenv/config'; +import status from '../config/status'; +import { User } from '../queries'; +import { checkCreateUpdateUserErrors, token as tokenHelper, urlHelper } from '../helpers'; + +const { CI } = process.env; +const { appUrl, travis } = urlHelper.frontend; +/** + * A class to handle users authentication using social media platform + */ +export default class AuthPassportController { + /** + * @param {object} profile social media user information + * @returns {object} a user object + */ + static getUser(profile = {}) { + const user = {}; + if (profile.displayName) { + const [firstName, lastName] = profile.displayName.split(' '); + user.firstName = firstName; + user.lastName = lastName; + } + if (profile.name) { + user.firstName = profile.name.givenName; + user.lastName = profile.name.familyName; + } + if (profile.emails) { + user.email = profile.emails[0].value; + } + user.username = profile.username || `${user.firstName}.${user.lastName}`; + return Object.keys(profile).length + ? { + ...user, + permissions: profile.permissions, + image: profile.photos[0].value, + accountProvider: profile.provider, + accountProviderUserId: profile.id, + isActive: true + } + : {}; + } + + /** + * @param {int} id + * @param {string} role + * @param {string} permissions + * @returns {string} a link to redirect the user + */ + static redirectOnSuccess({ id, role, permissions }) { + const token = tokenHelper.generate({ + id, + role, + permissions + }); + return `${(CI && travis) || appUrl}/auth?id=${id}&token=${token}`; + } + + /** + * @param {int} errorCode + * @param {object} errors + * @returns {string} a link to redirect the user + */ + static redirectOnError(errorCode, errors) { + let URL = `${(CI && travis) || appUrl}/auth?code=${errorCode}`; + URL = errors.email ? `${URL}&email=${errorCode}` : URL; + URL = errors.username ? `${URL}&username=${errorCode}` : URL; + return URL; + } + + /** + * @param {object} req + * @param {object} res + * @returns {object} an object containing user information + */ + static async loginOrSignup(req, res) { + if (req.user && !Object.keys(req.user).length) { + return res.redirect(`${(CI && travis) || appUrl}/auth?code=${status.BAD_REQUEST}`); + } + const findOrCreateUser = await User.findOrCreate( + { accountProvider: req.user.provider, accountProviderUserId: req.user.id }, + AuthPassportController.getUser(req.user) + ); + const { errors, code } = checkCreateUpdateUserErrors(findOrCreateUser.errors); + if (errors && code) { + return res.redirect(AuthPassportController.redirectOnError(code, errors)); + } + + return res.redirect(AuthPassportController.redirectOnSuccess(findOrCreateUser[0])); + } +} diff --git a/src/controllers/ChatController.js b/src/controllers/ChatController.js new file mode 100644 index 00000000..3a5a3c87 --- /dev/null +++ b/src/controllers/ChatController.js @@ -0,0 +1,88 @@ +import status from '../config/status'; +import { User, Chat } from '../queries'; + +/** + * A class to handle chat activities + */ +export default class ChatController { + /** + * @param {object} req + * @param {object} res + * @returns {object} an object containing chat details + */ + static async save(req, res) { + if (typeof req.body.message !== 'string' || !req.body.message) { + return res + .status(status.BAD_REQUEST) + .json({ errors: { message: 'the message should be a string and should not be empty' } }); + } + + const savedChat = await Chat.save(req.user.id, req.body.message); + if (savedChat.errors) { + if (savedChat.errors.name === 'SequelizeForeignKeyConstraintError') { + return res + .status(status.UNAUTHORIZED) + .json({ errors: { account: 'your account is not valid' } }); + } + return res.status(status.SERVER_ERROR).json({ errors: 'Oops, something went wrong' }); + } + + const findUser = await User.findOne({ id: req.user.id }); + delete findUser.password; + + req.io.emit('message', { ...savedChat, user: findUser }); + return res + .status(status.OK) + .json({ message: 'message successfully sent', chat: { ...savedChat, user: findUser } }); + } + + /** + * @param {object} req Request sent to the route + * @param {object} res Response from server + * @returns {object} Object representing the response returned + */ + static async getAll(req, res) { + let chats = []; + const { offset, limit } = req.query; + const savedChats = limit ? await Chat.getAll(offset, limit) : await Chat.getAll(offset); + + if (!savedChats.errors) { + savedChats.forEach((chat) => { + const user = chat.get().User.get(); + delete chat.dataValues.User; + chats = [...chats, { ...chat.get(), user }]; + }); + } + + return savedChats.errors + ? res + .status(status.SERVER_ERROR) + .json({ errors: 'Oops, something went wrong, please try again' }) + : res.status(status.OK).json({ chats }); + } + + /** + * @param {object} req Request sent to the route + * @param {object} res Response from server + * @returns {object} Object representing the response returned + */ + static async delete(req, res) { + const removedChat = req.user.role === 'normal' + ? await Chat.remove(req.params.chatId, req.user.id) + : await Chat.remove(req.params.chatId); + + if (removedChat.errors) { + return removedChat.errors.name === 'SequelizeDatabaseError' + ? res.status(status.BAD_REQUEST).json({ + errors: { chat: 'the provided chat ID is not valid, it should be an integer' } + }) + : res.status(status.SERVER_ERROR).json({ + errors: 'Oops, something went wrong, please try again' + }); + } + + return removedChat + ? res.status(status.OK).json({ message: 'chat successfully deleted' }) + : res.status(status.NOT_FOUND).json({ errors: { chat: 'chat not deleted' } }); + } +} diff --git a/src/controllers/CommentController.js b/src/controllers/CommentController.js new file mode 100644 index 00000000..d822d9f5 --- /dev/null +++ b/src/controllers/CommentController.js @@ -0,0 +1,108 @@ +/* eslint-disable import/named */ +import status from '../config/status'; +import * as comment from '../queries/comments'; +import * as editcomment from '../queries/comments/edits'; + +/** + * comment controller class + */ +export default class CommentController { + /** + * Create a comment + * @param { object } req the request. + * @param { object } res The response. + * @returns { object } the return object. + */ + static async create(req, res) { + const userId = req.user.id; + const { articleSlug } = req.params; + const { body } = req.body; + const response = await comment.create({ articleSlug, userId, body }); + const createdComment = await comment.getSingle(response.id); + return res.status(status.CREATED).json({ + message: 'Comment successfully created', + comment: createdComment + }); + } + + /** + * Get all comments. + * @param { object } req the request. + * @param { object } res The response. + * @returns { object } the return object. + */ + static async getAll(req, res) { + const { articleSlug } = req.params; + const allComments = await comment.getAll({ articleSlug }); + return res + .status(status.OK) + .json({ message: 'Comments fetched successfully', comments: allComments }); + } + + /** + * Delete one comment. + * @param { object } req the request. + * @param { object } res The response. + * @returns { object } the return object. + */ + static async delete(req, res) { + const { commentId } = req.params; + await comment.remove({ id: commentId }); + return res.status(status.OK).json({ message: 'Comment successfully deleted' }); + } + + /** + * Edit one comment + * @param { object } req the request. + * @param { object } res The response. + * @returns { object } the return object. + */ + static async editComment(req, res) { + const userId = req.user.id; + const { articleSlug, commentId } = req.params; + const findComment = await comment.getSingle({ articleSlug, id: commentId, userId }); + await editcomment.create({ + articleSlug: findComment.articleSlug, + userId: findComment.userId, + body: findComment.body, + commentId: findComment.id + }); + const { body } = req.body; + await comment.update({ body }, { id: commentId }); + return res.status(status.OK).json({ message: 'Comment edited successfully' }); + } + + /** + * Get all edits + * @param { object } req the request. + * @param { object } res The response. + * @returns { object } the return object. + */ + static async getAllEdit(req, res) { + const userId = req.user.id; + const { articleSlug, commentId } = req.params; + const newComment = { articleSlug, commentId, userId }; + const findAllEdit = await editcomment.getAll(newComment); + return res.status(status.OK).json({ message: 'All previous comments', history: findAllEdit }); + } + + /** + * Delete one edit from comment history. + * @param { object } req the request. + * @param { object } res The response. + * @returns { object } the return object. + */ + static async remove(req, res) { + const { id } = req.params; + let message = 'Comment removed from history successfully'; + const findEdit = await editcomment.getSingle({ id }); + if (!findEdit) { + message = 'Comment is not in history'; + return res.status(status.NOT_FOUND).json({ error: { message } }); + } + await editcomment.remove({ id }); + return res.status(status.OK).json({ + message + }); + } +} diff --git a/src/controllers/CommentLikeController.js b/src/controllers/CommentLikeController.js new file mode 100644 index 00000000..14bc0ad9 --- /dev/null +++ b/src/controllers/CommentLikeController.js @@ -0,0 +1,42 @@ +import status from '../config/status'; +import * as comment from '../queries/comments/likes'; +import updateCommentLikes from '../queries/comments/updateCommentLikes'; + +/** + * Class to control likes for comments + */ +export default class CommentLikeController { + /** + * + * @param {object } req the request + * @param { object} res the response + * @returns { object } returns the object + */ + static async create(req, res) { + const userId = req.user.id; + const { commentId, articleSlug } = req.params; + const like = { userId, commentId, articleSlug }; + const likeComment = await comment.createLike(like); + await updateCommentLikes(req); + return res.status(status.CREATED).send({ + message: 'You liked the comment', + createLike: likeComment + }); + } + + /** + * A method to display all likes on an article + * @param {object} req the request to get like + * @param {object} res the response of like from server + * @returns {object} the return after liking a particular article + */ + static async getAll(req, res) { + const { articleSlug, commentId } = req.params; + const findAllLikes = await comment.getAllLikes({ articleSlug, commentId }); + return res.status(status.OK).json({ + commentId, + likes: findAllLikes.length, + whoLiked: { userId: findAllLikes.map(value => value.userId) } + }); + } +} diff --git a/src/controllers/HighlightController.js b/src/controllers/HighlightController.js new file mode 100644 index 00000000..0a884222 --- /dev/null +++ b/src/controllers/HighlightController.js @@ -0,0 +1,87 @@ +import status from '../config/status'; +import { findOrCreate, getAll, deleteHighlight } from '../queries/highlights'; + +/** + * Highlight controller class + */ +export default class Highlights { + /** + * create a highlighted + * @param {object} req - User's request + * @param {object} res - Response's holder + * @returns {Object} response + */ + static async create(req, res) { + const [{ articleSlug }, userId] = [req.params, req.user.id]; + const { startIndex, stopIndex } = req.body; + const { highlightedText, comment, anchorKey } = req.body; + const contentLength = highlightedText.split('').length; + const indexesLength = stopIndex - startIndex; + if (contentLength !== indexesLength) { + return res.status(status.BAD_REQUEST).json({ + message: 'Sorry the length of your highlighted text does not match with start and end index' + }); + } + const created = await findOrCreate({ + articleSlug, + userId, + highlightedText, + startIndex, + stopIndex, + comment, + anchorKey + }); + return ( + (created.errors + && res.status(status.SERVER_ERROR).json({ message: 'Oops, something went wrong' })) + || res.status(status.CREATED).json({ message: 'You have highlighted this text', created }) + ); + } + + /** , + * Get Highlights + * @param {object} req + * @param {object} res + * @returns {object} highlights object + */ + static async getHighlights(req, res) { + const { articleSlug } = req.params; + + const highlights = await getAll({ + articleSlug + }); + + return ( + (highlights.length === 0 + && res.status(status.NOT_FOUND).json({ + message: 'You have no highlights' + })) + || res.status(status.OK).json({ + highlights + }) + ); + } + + /** , + * Remove Highlights + * @param {object} req + * @param {object} res + * @returns {object} message + */ + static async deleteHighlights(req, res) { + const { id, articleSlug } = req.params; + + const highlight = await deleteHighlight({ articleSlug, id }); + + return ( + (!highlight + && res.status(status.NOT_FOUND).json({ + message: 'Sorry, this highlight does not exist' + })) + || res.status(status.OK).json({ + message: 'You have successfully removed your highlight', + highlightId: id + }) + ); + } +} diff --git a/src/controllers/NotificationController.js b/src/controllers/NotificationController.js new file mode 100644 index 00000000..e732add1 --- /dev/null +++ b/src/controllers/NotificationController.js @@ -0,0 +1,164 @@ +import { Notification } from '../queries'; +import status from '../config/status'; + +/** + * A class to handle all notifications + */ +export default class NotificationController { + /** + * @param {object} req + * @param {object} res + * @return {object} return an object containing set configuration + */ + static async setConfig(req, res) { + const config = await Notification.config.create(req.user.id, req.body.config); + + if (config.errors && config.errors.name === 'SequelizeUniqueConstraintError') { + return res + .status(status.EXIST) + .json({ errors: { config: 'you already have set configurations' } }); + } + + return config.errors + ? res.status(status.SERVER_ERROR).json({ + errors: 'Oops, something went wrong, please try again' + }) + : res.status(status.CREATED).json({ + configuration: Object.keys(config).length ? JSON.parse(config.config) : null + }); + } + + /** + * @param {object} req + * @param {object} res + * @return {object} return an object containing set configuration + */ + static async getConfig(req, res) { + const config = await Notification.config.getOne(req.user.id); + + if (!config.errors) { + config.config = Object.keys(config).length ? JSON.parse(config.config) : null; + return res.status(status.OK).json(config); + } + return res + .status(status.SERVER_ERROR) + .json({ errors: 'Oops, something went wrong, please try again' }); + } + + /** + * @param {object} req + * @param {object} res + * @return {object} return an object containing the updated configuration + */ + static async updateConfig(req, res) { + const config = await Notification.config.update(req.user.id, req.body.config); + + if (config.errors) { + return res + .status(status.SERVER_ERROR) + .json({ errors: 'Oops, something went wrong, please try again' }); + } + + return config.config + ? res.status(status.OK).json({ configuration: JSON.parse(config.config) }) + : res.status(status.BAD_REQUEST).json({ + errors: { notification: "you don't have set configuration" } + }); + } + + /** + * @param {object} req + * @param {object} res + * @return {array} return an array containing notifications + */ + static async getAll(req, res) { + const { url, query, user } = req; + const [offset, limit] = [query.offset || 0, query.limit || 20]; + const notificationStatus = (url.search(/\/unseen/g) >= 0 && 'unseen') || (url.search(/\/seen/g) >= 0 && 'seen'); + + const notifications = await Notification.getAll(user.id, notificationStatus, offset, limit); + + return !notifications.errors + ? res.status(status.OK).json({ notifications }) + : res + .status(status.SERVER_ERROR) + .json({ errors: 'Oops, something went wrong, please try again' }); + } + + /** + * @param {object} req + * @param {object} res + * @return {object} return an object containing one notification + */ + static async getOne(req, res) { + const notification = await Notification.getOne(req.user.id, req.params.notificationId); + const errors = notification.errors || null; + + return ( + (errors + && errors.name === 'SequelizeDatabaseError' + && res.status(status.BAD_REQUEST).json({ + errors: { notification: 'the provided notification ID should be an integer' } + })) + || (errors + && res.status(status.SERVER_ERROR).json({ + errors: 'Oops, something went wrong, please try again' + })) + || res.status(status.OK).json({ notification }) + ); + } + + /** + * @param {object} req + * @param {object} res + * @return {object} return an object containing the updated notification + */ + static async update(req, res) { + const [notificationStatus, notificationId, preference] = [ + (req.url.search(/\/unseen/g) >= 0 && 'unseen') || (req.url.search(/\/seen/g) >= 0 && 'seen'), + req.params.notificationId, + req.body.preference + ]; + const notifications = await Notification.update(req.user.id, notificationId, { + status: notificationStatus, + preference + }); + return ( + (notifications.errors + && ((notifications.errors.name === 'SequelizeDatabaseError' + && res.status(status.BAD_REQUEST).json({ + errors: { notification: 'the provided notification ID should be an integer' } + })) + || res.status(status.SERVER_ERROR).json({ + errors: 'Oops, something went wrong, please try again' + }))) + || res.status(notifications.length ? status.OK : status.NOT_FOUND).json({ notifications }) + ); + } + + /** + * @param {object} req Request sent to the route + * @param {object} res Response from server + * @returns {object} Object representing the response returned + */ + static async delete(req, res) { + const deleted = await Notification.remove(req.params.notificationId, req.user.id); + const errors = deleted.errors || null; + + return errors + ? (errors.name === 'SequelizeDatabaseError' + && res.status(status.BAD_REQUEST).json({ + errors: { + notification: 'the provided notification ID is not valid, it should be an integer' + } + })) + || res + .status(status.SERVER_ERROR) + .json({ errors: 'Oops, something went wrong, please try again' }) + : (!deleted + && res.status(status.NOT_FOUND).json({ + errors: { notification: 'notification not deleted' } + })) + || res.status(status.OK).json({ message: 'notification successfully deleted' }); + } +} diff --git a/src/controllers/PermissionController.js b/src/controllers/PermissionController.js new file mode 100644 index 00000000..e1edda4f --- /dev/null +++ b/src/controllers/PermissionController.js @@ -0,0 +1,56 @@ +import { User } from '../queries'; +import status from '../config/status'; + +// import * as validate from '../helpers/validation'; + +/** + * A class to handle user local authentication + */ +export default class PermissionController { + /** + * @param {object} req + * @param {object} res + * @return {object} user information & token + */ + static async create(req, res) { + const newPermissions = await User.permissions.create( + req.body.userType, + JSON.stringify(req.body.permissions) + ); + let errors = newPermissions.errors || null; + if (errors) { + errors = errors.name === 'SequelizeUniqueConstraintError' + ? { + code: status.EXIST, + error: { permissions: 'Sorry, this permission is already defined' } + } + : { code: status.SERVER_ERROR, errors: 'ooppps something went wrong' }; + } + return errors + ? res.status(errors.code).json({ errors: errors.error }) + : res.status(status.CREATED).json({ + permissions: newPermissions + }); + } + + /** + * @param {object} req + * @param {object} res + * @return {object} return an object containing set configuration + */ + static async findAll(req, res) { + const { userType } = req.params; + const permissions = (userType && (await User.permissions.findAll({ userType }))) + || (await User.permissions.findAll()); + + return ( + (permissions.length + && res.status(status.OK).json({ + permissions + })) + || res.status(status.NOT_FOUND).json({ + message: 'No permission found' + }) + ); + } +} diff --git a/src/controllers/RatingController.js b/src/controllers/RatingController.js new file mode 100644 index 00000000..eb16ff63 --- /dev/null +++ b/src/controllers/RatingController.js @@ -0,0 +1,87 @@ +import status from '../config/status'; + +import { Article } from '../queries'; + +/** + * RatingController: Class that handles article rating + */ +class RatingController { + /** + * @param {object} req Request sent to the route + * @param {object} res Response from server + * @returns {object} Object representing the response returned + */ + static async create(req, res) { + const data = { + rating: req.body.rating, + articleId: req.article.id, + userId: req.user.id + }; + const response = await Article.rate.create(data); + return response.errors + ? res.status(status.BAD_REQUEST).send({ response }) + : res.status(response === 'created' ? status.CREATED : status.OK).send({ + rating: { + message: + response === 'created' + ? 'Thank you for rating this article' + : 'Your article rating has been updated' + } + }); + } + + /** + * @param {object} req Request sent to the route + * @param {object} res Response from server + * @returns {object} Object representing the response returned + */ + static async get(req, res) { + const rating = await Article.rate.get({ articleId: req.article.id }); + return res.status(status.OK).send({ + article: rating + }); + } + + /** + * @param {object} req Request sent to the route + * @param {object} res Response from server + * @returns {object} Object representing the response returned + */ + static async sortArticlesByRating(req, res) { + const { limit, offset } = req.query; + const articles = await Article.rate.sort(parseInt(limit, 0) || 20, offset || 0, { + keyword: req.query.keyword, + author: req.query.author, + tag: req.query.tag + }); + return articles.length >= 1 && !!articles + ? res.status(status.OK).send({ + articles, + articlesCount: articles.length + }) + : res.status(status.NOT_FOUND).send({ message: 'No articles found' }); + } + + /** + * @param {object} req Request sent to the route + * @param {object} res Responfromse server + * @returns {object} Object representing the response returned + */ + static async ArticleRatings(req, res) { + const { limit, offset } = req.query; + const articleRatings = await Article.rate.articleRatings( + parseInt(limit, 0) || 20, + offset || 0, + req.article.id + ); + return Object.keys(articleRatings).length + ? res.status(status.OK).send({ + articleRatings, + articlesCount: articleRatings.length + }) + : res.status(status.NOT_FOUND).send({ message: 'No rating for this article' }); + } +} + +// validation +export default RatingController; diff --git a/src/controllers/ReadingStatController.js b/src/controllers/ReadingStatController.js new file mode 100644 index 00000000..7a3f90c1 --- /dev/null +++ b/src/controllers/ReadingStatController.js @@ -0,0 +1,46 @@ +import { getAllRatings, createRatings } from '../queries'; +import status from '../config/status'; + +/** + * A class to handle Reading Stats for users + */ +export default class ReadingStat { + /** + * Save reading stats + * @param {object} req + * @param {object} res + * @returns {object} stats object + */ + static async create(req, res) { + const userId = req.user.id; + const { slug } = req.params; + + const saveStat = await createRatings({ + userId, + articleSlug: slug + }); + + return res.status(status.OK).json({ + message: saveStat + }); + } + + /** + * Get reading stats + * @param {Object} req express request + * @param {Object} res express response + * @returns {Array} user reading statistics + */ + static async getAll(req, res) { + const userId = req.user.id; + + const readingStats = await getAllRatings({ + userId + }); + + return res.status(status.OK).json({ + message: 'Reading Stats fetched', + readingStats + }); + } +} diff --git a/src/controllers/ReportController.js b/src/controllers/ReportController.js new file mode 100644 index 00000000..2ffde64e --- /dev/null +++ b/src/controllers/ReportController.js @@ -0,0 +1,74 @@ +import status from '../config/status'; +import * as report from '../queries/reports'; +/** + * A class to control the report + */ +export default class ReportController { + /** + * A method to create a report + * @param {object} req the request + * @param {object } res the response + * @returns {object} a response to the client + */ + static async createReport(req, res) { + const userId = req.user.id; + const { articleSlug } = req.params; + const { title, body, type } = req.body; + const newReport = await report.create({ + userId, + articleSlug, + title, + body, + type + }); + res.status(status.CREATED).json({ + message: 'Report created successfully', + report: newReport + }); + } + + /** + * A method to get all reports for a specific article + * @param {object} req the request + * @param {object } res the response + * @returns {object} a response to the client + */ + static async getAll(req, res) { + const findAllReports = await report.getAll( + req.params.articleSlug ? { articleSlug: req.params.articleSlug } : null + ); + return res.status(status.OK).json({ + message: 'fetched all reports successfully', + reports: findAllReports + }); + } + + /** + * A method to get a single report + * @param {object} req the request + * @param {object } res the response + * @returns {object} a response to the client + */ + static async getSingle(req, res) { + const { articleSlug, reportId } = req.params; + const newreport = { articleSlug, id: reportId }; + const findSingle = await report.getSingle(newreport); + return res + .status(status.OK) + .json({ message: 'Report fetched Successfully', report: findSingle }); + } + + /** + * A method to delete a report + * @param {object} req the request + * @param {object } res the response + * @returns {object} a response to the client + */ + static async deleteSingle(req, res) { + const { reportId } = req.params; + const deleteSingle = await report.remove({ id: reportId }); + return res + .status(status.OK) + .json({ message: 'Report deleted Successfully', report: deleteSingle, reportId }); + } +} diff --git a/src/controllers/TagController.js b/src/controllers/TagController.js new file mode 100644 index 00000000..bfc5f6a1 --- /dev/null +++ b/src/controllers/TagController.js @@ -0,0 +1,43 @@ +import status from '../config/status'; + +import { Tag } from '../queries'; + +/** + * A class to handle actions performed on article tags + */ +class TagsController { + /** + * @param {object} req Request sent to the route + * @param {object} res Response from server + * @returns {object} Object representing the response returned + */ + static async update(req, res) { + const action = req.method === 'DELETE' ? 'delete' : 'update'; + const response = await Tag.update(req.body.tagList, req.params.slug, action); + + let statusCode = status.BAD_REQUEST; + if (action === 'update') { + statusCode = status.CREATED; + } else if (action === 'delete') { + statusCode = status.OK; + } + return res.status(statusCode).send({ + response + }); + } + + /** + * @param {object} req Request sent to the route + * @param {object} res Response from server + * @returns {object} Object representing the response returned + */ + static async getAll(req, res) { + const tags = await Tag.get(); + return res.status(status.OK).send({ + tags + }); + } +} + +// validation +export default TagsController; diff --git a/src/controllers/UploadController.js b/src/controllers/UploadController.js new file mode 100644 index 00000000..22968d48 --- /dev/null +++ b/src/controllers/UploadController.js @@ -0,0 +1,87 @@ +import dotenv from 'dotenv'; +import status from '../config/status'; +import { Gallery, Article } from '../queries'; + +dotenv.config(); +const { IMAGE_BASE_URL, NODE_ENV } = process.env; +/** + * A class to upload image + */ +export default class UploadController { + /** + * Upload image, and save it to gallery table + * @param {object} req Request sent to the route + * @param {object} res Response from server + * @returns {object} Object representing the response returned + */ + static async save(req, res) { + const image = req.files && req.files[0]; + return typeof image === 'object' && typeof image !== 'boolean' && NODE_ENV !== 'test' + ? (await Gallery.save({ + image: `${image.version}/${image.public_id}.${image.format}`, + userId: req.user.id + })) + && res.status(status.CREATED).json({ + image: { + original: `v${image.version}/${image.public_id}.${image.format}`, + thumbnail: `${IMAGE_BASE_URL}/w_600/v${image.version}/${image.public_id}.${ + image.format + }`, + square: `${IMAGE_BASE_URL}/w_320,ar_1:1,c_fill,g_auto,e_art:hokusai/v${ + image.version + }/${image.public_id}.${image.format}`, + circle: `${IMAGE_BASE_URL}/w_200,c_fill,ar_1:1,g_auto,r_max,b_rgb:262c35/v${ + image.version + }/${image.public_id}.${image.format}` + } + }) + : res + .status(status.BAD_REQUEST) + .json({ errors: { image: 'sorry, you did not provide image to be uploaded' } }); + } + + /** + * Get image gallery for a given author + * @param {object} req Request sent to the route + * @param {object} res Response from server + * @returns {object} Object representing the response returned + */ + static async get(req, res) { + const { limit, offset } = req.query; + const galleries = await Gallery.get(parseInt(limit, 0) || 10, offset || 0, { + userId: req.user.id + }); + if (galleries.length >= 1 && !!galleries) { + res.status(status.OK).send({ + galleries, + galleryCount: galleries.length + }); + } else { + res.status(status.NOT_FOUND).send({ + errors: { + gallery: "You don't have image gallery yet" + } + }); + } + } + + /** + * Update article coverUrl + * @param {object} req Request sent to the route + * @param {object} res Response from server + * @returns {object} Object representing the response returned + */ + static async setCover(req, res) { + const message = await Article.updateCover( + { coverUrl: req.body.coverUrl }, + req.params.slug, + req.user.id + ); + if (message[0] === 1) { + return res.status(status.OK).send({ coverUrl: 'CoverUrl has been updated' }); + } + return res + .status(status.BAD_REQUEST) + .send({ errors: { coverUrl: 'coverUrl not updated, try again' } }); + } +} diff --git a/src/controllers/UserController.js b/src/controllers/UserController.js new file mode 100644 index 00000000..8cf579ac --- /dev/null +++ b/src/controllers/UserController.js @@ -0,0 +1,240 @@ +import 'dotenv/config'; +import { User } from '../queries'; +import status from '../config/status'; +import { + checkCreateUpdateUserErrors, sendMail, token as tokenHelper, urlHelper +} from '../helpers'; + +const { CI } = process.env; +const { appUrl, travis } = urlHelper.frontend; + +/** + * A class to handle user local authentication + */ +export default class UserController { + /** + * @param {object} req + * @param {object} res + * @return {object} return an object containing the updated profile + */ + static async update(req, res) { + const userId = req.userId || req.user.id; + const updatedUser = await User.update(req.body, { id: userId }); + + if (updatedUser.errors) { + const errors = checkCreateUpdateUserErrors(updatedUser.errors); + return res.status(errors.code).json({ errors: errors.errors }); + } + + delete updatedUser.password; + + if (req.changeEmail.newEmail) { + await sendMail(req.changeEmail.newEmail, 'updateEmail', { + userId, + email: req.changeEmail.newEmail + }); + } + + return res.status(status.OK).json({ + message: `Profile successfully updated. ${req.changeEmail.message}`, + user: updatedUser + }); + } + + /** + * @param {object} req + * @param {object} res + * @return {object} return all users in database + */ + static async getAllAuthors(req, res) { + const role = 'normal'; + const offset = req.query.offset || 0; + const limit = req.query.limit || 20; + const authors = (await User.getAllUser({ role }, offset, limit)).map( + author => delete author.get().password && author + ); + return res.status(status.OK).json({ + authors + }); + } + + /** + * @param {object} req + * @param {object} res + * @return {object} return all users in database + */ + static async getAll(req, res) { + const [offset, limit] = [req.query.offset || 0, req.query.limit || 20]; + const users = await User.getAllUser({}, offset, limit); + + return res.status(status.OK).json({ + users: users.map((user) => { + const { password, ...userInfo } = user.get(); + return userInfo; + }) + }); + } + + /** + * @param {object} req + * @param {object} res + * @return {object} return all users in database + */ + static async getAllByUsername(req, res) { + const { username } = req.params; + const { offset, limit } = req.query; + const users = await User.searchUsersByUsername(username, offset, limit); + + return ( + (users + && users.length + && res.status(status.OK).json({ + users: users.map(user => delete user.get().password && user) + })) + || res.status(status.NOT_FOUND).json({ errors: { user: 'no user with this username found' } }) + ); + } + + /** + * Make a user an admin + * @param {Object} req express request object + * @param {Object} res express response object + * @returns {*} success response + * @throws {*} error if database error + */ + static async updateUserRole(req, res) { + const { role } = req.body; + const { username } = req.params; + + const user = await User.findOne({ username }); + if (!user.errors && !Object.keys(user).length) { + return res + .status(status.NOT_FOUND) + .json({ message: `This user with the username ${username} does not exist` }); + } + if (user.role === role) { + return res.status(status.EXIST).send({ message: 'The user already has this role' }); + } + if (!req.body.role) { + return res.status(status.BAD_REQUEST).send({ message: 'the role can not be empty' }); + } + const updatedUser = await User.update({ role }, { username }); + delete updatedUser.password; + return res.status(status.OK).json({ message: 'roles updated successfully', updatedUser }); + } + + // follow user + /** + * @description function to create user follows + * @param {object} req request from user + * @param {object} res server response + * @returns {object} true + */ + static async follow(req, res) { + const { username } = req.params; + const checkUser = await User.findOne({ username }); + if (checkUser.id === req.user.id) { + return res + .status(status.BAD_REQUEST) + .json({ errors: { follow: 'You can not follow your self ' } }); + } + const follow = await User.follow.add({ + followed: checkUser.id, + userId: req.user.id + }); + if (follow.errors) { + return follow.errors.name === 'SequelizeUniqueConstraintError' + ? res + .status(status.EXIST) + .send({ errors: { follow: `You are already following "${username}"` } }) + : res.status(status.SERVER_ERROR).json({ errors: 'oops, something went wrong' }); + } + return res.status(status.CREATED).json({ + message: `now you are following ${checkUser.username}`, + follow: { ...follow, followedUser: checkUser } + }); + } + + // unFollow user + /** + * @description function to allow user to unfollow users + * @param {object} req user request + * @param {object} res response from server + * @returns {object} true + */ + static async unfollow(req, res) { + const [username, user] = [req.params.username, req.user]; + const checkUser = await User.findOne({ username }); + + const hasUnfollowed = Object.keys(checkUser).length + ? await User.follow.remove({ userId: user.id, followed: checkUser.id }) + : null; + if (hasUnfollowed && hasUnfollowed.errors) { + return res.status(status.SERVER_ERROR).json({ errors: 'oops, something went wrong!!' }); + } + return hasUnfollowed + ? res.status(status.OK).json({ + message: `you unfollowed ${username}`, + followed: checkUser.id + }) + : res + .status(status.BAD_REQUEST) + .json({ errors: { follow: `you are not following "${username}"` } }); + } + + /** + * @description function to fetch users'followers + * @param {object} req + * @param {object} res + * @returns {object} followers + */ + static async followers(req, res) { + const { id } = req.user; + const followers = await User.follow.getAll({ followed: id }); + return followers.length + ? res.status(status.OK).json({ + message: 'Followers', + followers: followers.map(follower => delete follower.get().followedUser && follower) + }) + : res.status(status.NOT_FOUND).json({ + errors: { follows: "You don't have followers" } + }); + } + + /** + * @description function to fetch all authors who user follow + * @param {object} req + * @param {object} res + * @returns {object} followers + */ + static async following(req, res) { + const following = await User.follow.getAll({ userId: req.user.id }); + const follows = following.map(followed => delete followed.get().follower && followed); + if (following.length) { + return res.status(status.OK).json({ + message: 'Following', + following: follows + }); + } + return res.status(status.NOT_FOUND).json({ + errors: { follows: "You don't follow any one" } + }); + } + + /** + * @description confirm email update + * @param {object} req + * @param {object} res + * @returns {object} redirection link + */ + static async confirmEmailUpdate(req, res) { + const redirectUrl = (CI && travis) || appUrl; + const decodedToken = tokenHelper.decode(req.params.token); + + if (!decodedToken.errors || decodedToken.email) { + await User.update({ email: decodedToken.email }, { id: decodedToken.userId }); + return res.redirect(`${redirectUrl}/profile?email=${decodedToken.email}`); + } + return res.redirect(`${redirectUrl}/profile?token=${status.UNAUTHORIZED}`); + } +} diff --git a/src/helpers/articles/counter.js b/src/helpers/articles/counter.js new file mode 100644 index 00000000..3ee75e4d --- /dev/null +++ b/src/helpers/articles/counter.js @@ -0,0 +1,6 @@ +import { Article } from '../../queries'; + +export default async (status) => { + const counter = await Article.getArticlesCounter(status); + return counter; +}; diff --git a/src/helpers/articles/index.js b/src/helpers/articles/index.js new file mode 100644 index 00000000..91653d6a --- /dev/null +++ b/src/helpers/articles/index.js @@ -0,0 +1,5 @@ +import counter from './counter'; + +export default { + counter +}; diff --git a/src/helpers/checkCreateUpdateUserErrors.js b/src/helpers/checkCreateUpdateUserErrors.js new file mode 100644 index 00000000..f835bb11 --- /dev/null +++ b/src/helpers/checkCreateUpdateUserErrors.js @@ -0,0 +1,24 @@ +import status from '../config/status'; + +/** + * @param {object} err + * @returns {object} an object containing descriptive error messages + */ +export default (err = {}) => { + const errors = {}; + if (err.name === 'SequelizeUniqueConstraintError') { + if (err.fields.email) { + errors.email = 'email already used'; + } + + if (err.fields.username) { + errors.username = 'username already used'; + } + + return { errors, code: status.EXIST }; + } + return { + errors: err.message, + code: err.name === 'SequelizeValidationError' ? status.BAD_REQUEST : status.SERVER_ERROR + }; +}; diff --git a/src/helpers/clearInvalidToken.js b/src/helpers/clearInvalidToken.js new file mode 100644 index 00000000..7031d622 --- /dev/null +++ b/src/helpers/clearInvalidToken.js @@ -0,0 +1,9 @@ +import { Token } from '../queries'; +/** + * @param {int} userId + * @return {integer} the number of destroyed tokens + */ +export default async (userId) => { + const destroyedToken = typeof userId === 'number' ? await Token.destroy(userId) : 0; + return !destroyedToken.errors ? destroyedToken : null; +}; diff --git a/src/helpers/errorHandler.js b/src/helpers/errorHandler.js new file mode 100644 index 00000000..74dba843 --- /dev/null +++ b/src/helpers/errorHandler.js @@ -0,0 +1,25 @@ +import status from '../config/status'; + +/** + * Author: Gilles Kagarama + * Joi error logger + */ +class Error { + /** + * Joi error logger + * @param {object} res response + * @param {object} result Results passed + * @returns {object} Object representing the response returned + */ + static joiErrorHandler(res, result) { + const errorArray = []; + result.error.details.forEach((value) => { + errorArray.push(value.message.replace(/"/g, '')); + }); + return res.status(status.BAD_REQUEST).send({ + errors: errorArray + }); + } +} + +export default Error; diff --git a/src/helpers/eventEmitter.js b/src/helpers/eventEmitter.js new file mode 100644 index 00000000..47429004 --- /dev/null +++ b/src/helpers/eventEmitter.js @@ -0,0 +1,3 @@ +import EventEmitter from 'events'; + +export default new EventEmitter(); diff --git a/src/helpers/eventListener.js b/src/helpers/eventListener.js new file mode 100644 index 00000000..043e846b --- /dev/null +++ b/src/helpers/eventListener.js @@ -0,0 +1,7 @@ +import eventEmitter from './eventEmitter'; +import publishArticle from './notifications/publishArticle'; +import commentArticle from './notifications/commentArticle'; + +eventEmitter.on('publishArticle', publishArticle); +eventEmitter.on('commentArticle', commentArticle); +eventEmitter.on('error', err => process.stdout.write('Oops! an event error occurred') && err); diff --git a/src/helpers/factory/article.js b/src/helpers/factory/article.js new file mode 100644 index 00000000..dd7fac95 --- /dev/null +++ b/src/helpers/factory/article.js @@ -0,0 +1,16 @@ +// eslint-disable-next-line import/no-extraneous-dependencies +import { Factory } from 'rosie'; +import Chance from 'chance'; + +const chance = new Chance(); + +export default Factory.define('article') + .attr('id', 1) + .attr('userId', 1) + .attr('title', chance.sentence({ words: 5, min: 5, max: 255 })) + .attr('description', chance.sentence({ min: 5, max: 255 })) + .attr('body', chance.paragraph({ sentences: 10, min: 5, max: 300 })) + .attr('coverUrl', 'placeholer.jpg') + .attr('tagList', ['Capetown', 'Cairo']) + .attr('slug', 'rosie-make-it-easy-1dh6jv9cn4sz') + .attr('readTime', 0); diff --git a/src/helpers/factory/comment.js b/src/helpers/factory/comment.js new file mode 100644 index 00000000..fd1c26cd --- /dev/null +++ b/src/helpers/factory/comment.js @@ -0,0 +1,9 @@ +// eslint-disable-next-line import/no-extraneous-dependencies +import { Factory } from 'rosie'; +import Chance from 'chance'; + +const chance = new Chance(); + +export default Factory.define('comment') + .sequence('id') + .attr('body', chance.paragraph({ sentences: 1 })); diff --git a/src/helpers/factory/editComment.js b/src/helpers/factory/editComment.js new file mode 100644 index 00000000..151f1a80 --- /dev/null +++ b/src/helpers/factory/editComment.js @@ -0,0 +1,8 @@ +import { Factory } from 'rosie'; +import Chance from 'chance'; + +const chance = new Chance(); + +export default Factory.define('editComment') + .sequence('id') + .attr('body', chance.paragraph({ sentences: 1 })); diff --git a/src/helpers/factory/highlight.js b/src/helpers/factory/highlight.js new file mode 100644 index 00000000..a74b5d5c --- /dev/null +++ b/src/helpers/factory/highlight.js @@ -0,0 +1,11 @@ +// eslint-disable-next-line import/no-extraneous-dependencies +import { Factory } from 'rosie'; +import Chance from 'chance'; + +// const chance = new Chance(); + +export default Factory.define('highlight') + .attr('highlightedText', 'on sera ensemble bientotssssss') + .attr('startIndex', 0) + .attr('stopIndex', 29) + .attr('comment', 'welcomme to the party'); diff --git a/src/helpers/factory/index.js b/src/helpers/factory/index.js new file mode 100644 index 00000000..3b59899e --- /dev/null +++ b/src/helpers/factory/index.js @@ -0,0 +1,37 @@ +import user from './user'; +import userNormal from './userNormal'; +import userAdmin from './userAdmin'; +import userFacebook from './userFacebook'; +import userGoogle from './userGoogle'; +import userTwitter from './userTwitter'; +import article from './article'; +import readtime from './readtime'; +import token from './token'; +import comment from './comment'; +import report from './report'; +import editComment from './editComment'; +import permissionsNormal from './permissionsNormal'; +import permissionsAdmin from './permissionsAdmin'; +import highlight from './highlight'; +import notification from './notification'; +import notificationConfig from './notificationConfig'; + +export { + user, + userNormal, + userAdmin, + userFacebook, + userTwitter, + userGoogle, + article, + comment, + readtime, + token, + report, + editComment, + permissionsNormal, + permissionsAdmin, + highlight, + notification, + notificationConfig +}; diff --git a/src/helpers/factory/notification.js b/src/helpers/factory/notification.js new file mode 100644 index 00000000..6d59bc02 --- /dev/null +++ b/src/helpers/factory/notification.js @@ -0,0 +1,9 @@ +// eslint-disable-next-line import/no-extraneous-dependencies +import { Factory } from 'rosie'; +import Chance from 'chance'; + +const chance = new Chance(); + +export default Factory.define('notification') + .attr('userId', 1) + .attr('message', chance.paragraph({ sentences: 1 })); diff --git a/src/helpers/factory/notificationConfig.js b/src/helpers/factory/notificationConfig.js new file mode 100644 index 00000000..530fd898 --- /dev/null +++ b/src/helpers/factory/notificationConfig.js @@ -0,0 +1,17 @@ +// eslint-disable-next-line import/no-extraneous-dependencies +import { Factory } from 'rosie'; + +export default Factory.define('notificationConfig').attr('config', { + inApp: { + articles: { + show: true, + on: ['publish', 'comment', 'like'] + } + }, + email: { + articles: { + show: true, + on: ['publish'] + } + } +}); diff --git a/src/helpers/factory/permissionsAdmin.js b/src/helpers/factory/permissionsAdmin.js new file mode 100644 index 00000000..9c0993b2 --- /dev/null +++ b/src/helpers/factory/permissionsAdmin.js @@ -0,0 +1,15 @@ +// eslint-disable-next-line import/no-extraneous-dependencies +import { Factory } from 'rosie'; + +export default Factory.define('permissionsAdmin') + .attr('userType', 'admin') + .attr( + 'permissions', + JSON.stringify({ + articles: ['read', 'create', 'edit', 'delete'], + comments: ['read', 'create', 'edit', 'delete'], + tags: ['read', 'create', 'edit', 'delete'], + users: ['read', 'create', 'edit', 'delete'], + permissions: ['read', 'create', 'edit', 'delete'] + }) + ); diff --git a/src/helpers/factory/permissionsNormal.js b/src/helpers/factory/permissionsNormal.js new file mode 100644 index 00000000..b556ffc7 --- /dev/null +++ b/src/helpers/factory/permissionsNormal.js @@ -0,0 +1,14 @@ +// eslint-disable-next-line import/no-extraneous-dependencies +import { Factory } from 'rosie'; + +export default Factory.define('permissionsNormal') + .attr('userType', 'normal') + .attr( + 'permissions', + JSON.stringify({ + articles: ['read', 'create', 'delete', 'edit'], + comments: ['read', 'create', 'delete', 'edit'], + tags: ['read', 'create', 'delete'], + users: ['read', 'create', 'edit', 'delete'] + }) + ); diff --git a/src/helpers/factory/readtime.js b/src/helpers/factory/readtime.js new file mode 100644 index 00000000..3397f1aa --- /dev/null +++ b/src/helpers/factory/readtime.js @@ -0,0 +1,10 @@ +// eslint-disable-next-line import/no-extraneous-dependencies +import { Factory } from 'rosie'; + +export default Factory.define('readtime') + .attr('oneimage', 'Rosie, make it easy ') + .attr('twoimage', 'Rosie, make it easy ') + .attr( + 'threeimage', + 'Rosie, make it easy ' + ); diff --git a/src/helpers/factory/report.js b/src/helpers/factory/report.js new file mode 100644 index 00000000..58ebcbf0 --- /dev/null +++ b/src/helpers/factory/report.js @@ -0,0 +1,9 @@ +import { Factory } from 'rosie'; +import Chance from 'chance'; + +const chance = new Chance(); + +export default Factory.define('comment') + .sequence('id') + .attr('title', chance.word({ length: 5 })) + .attr('body', chance.paragraph({ sentences: 2 })); diff --git a/src/helpers/factory/token.js b/src/helpers/factory/token.js new file mode 100644 index 00000000..8094feb5 --- /dev/null +++ b/src/helpers/factory/token.js @@ -0,0 +1,5 @@ +// eslint-disable-next-line import/no-extraneous-dependencies +import { Factory } from 'rosie'; +import tokenGenarator from '../tokens/tokenGenerator'; + +export default Factory.define('token').attr('token', tokenGenarator({ id: 0, role: 'admin' })); diff --git a/src/helpers/factory/user.js b/src/helpers/factory/user.js new file mode 100644 index 00000000..75962ca8 --- /dev/null +++ b/src/helpers/factory/user.js @@ -0,0 +1,24 @@ +// eslint-disable-next-line import/no-extraneous-dependencies +import { Factory } from 'rosie'; +import Chance from 'chance'; + +const chance = new Chance(); + +export default Factory.define('user') + .sequence('id') + .attr('firstName', chance.first()) + .attr('lastName', chance.last()) + .attr('username', chance.word({ length: 5 })) + .attr('email', chance.email({ domain: 'example.com' })) + .attr('password', 'Baaa1234!') + .attr('role', 'admin') + .attr( + 'permissions', + JSON.stringify({ + articles: ['read', 'create', 'edit', 'delete'], + comments: ['read', 'create', 'edit', 'delete'], + tags: ['read', 'edit', 'create', 'delete'], + users: ['read', 'create', 'edit', 'delete'], + permissions: ['read', 'create', 'edit', 'delete'] + }) + ); diff --git a/src/helpers/factory/userAdmin.js b/src/helpers/factory/userAdmin.js new file mode 100644 index 00000000..723a248e --- /dev/null +++ b/src/helpers/factory/userAdmin.js @@ -0,0 +1,24 @@ +// eslint-disable-next-line import/no-extraneous-dependencies +import { Factory } from 'rosie'; +import Chance from 'chance'; + +const chance = new Chance(); + +export default Factory.define('user') + .sequence('id') + .attr('firstName', chance.first()) + .attr('lastName', chance.last()) + .attr('username', chance.word({ length: 5 })) + .attr('email', chance.email({ domain: 'example.com' })) + .attr('password', 'Baaa1234!') + .attr('role', 'admin') + .attr( + 'permissions', + JSON.stringify({ + articles: ['read', 'delete'], + comments: ['read', 'delete'], + tags: ['read', 'create', 'delete'], + users: ['read', 'create', 'edit', 'delete'], + permissions: ['read', 'create', 'edit', 'delete'] + }) + ); diff --git a/src/helpers/factory/userFacebook.js b/src/helpers/factory/userFacebook.js new file mode 100644 index 00000000..459b7ea8 --- /dev/null +++ b/src/helpers/factory/userFacebook.js @@ -0,0 +1,21 @@ +// eslint-disable-next-line import/no-extraneous-dependencies +import { Factory } from 'rosie'; +import Chance from 'chance'; + +const chance = new Chance(); + +export default Factory.define('user') + .attr('id', `${chance.integer({ min: 100, max: 100000 })}`) + .attr('name', { familyName: chance.last(), givenName: chance.first() }) + .attr('emails', [{ value: chance.email({ domain: 'example.com' }) }]) + .attr('photos', [{ value: 'image.jpg' }]) + .attr('provider', 'facebook') + .attr( + 'permissions', + JSON.stringify({ + articles: ['read', 'create', 'delete', 'edit'], + comments: ['read', 'create', 'delete', 'edit'], + tags: ['read', 'create', 'delete'], + users: ['read', 'create', 'edit', 'delete'] + }) + ); diff --git a/src/helpers/factory/userGoogle.js b/src/helpers/factory/userGoogle.js new file mode 100644 index 00000000..a213063d --- /dev/null +++ b/src/helpers/factory/userGoogle.js @@ -0,0 +1,21 @@ +// eslint-disable-next-line import/no-extraneous-dependencies +import { Factory } from 'rosie'; +import Chance from 'chance'; + +const chance = new Chance(); + +export default Factory.define('user') + .attr('id', `${chance.integer({ min: 100, max: 100000 })}`) + .attr('name', { familyName: chance.last(), givenName: chance.first() }) + .attr('emails', [{ value: chance.email({ domain: 'example.com' }) }]) + .attr('photos', [{ value: 'image.jpg' }]) + .attr('provider', 'google') + .attr( + 'permissions', + JSON.stringify({ + articles: ['read', 'create', 'delete', 'edit'], + comments: ['read', 'create', 'delete', 'edit'], + tags: ['read', 'create', 'delete'], + users: ['read', 'create', 'edit', 'delete'] + }) + ); diff --git a/src/helpers/factory/userNormal.js b/src/helpers/factory/userNormal.js new file mode 100644 index 00000000..af517991 --- /dev/null +++ b/src/helpers/factory/userNormal.js @@ -0,0 +1,23 @@ +// eslint-disable-next-line import/no-extraneous-dependencies +import { Factory } from 'rosie'; +import Chance from 'chance'; + +const chance = new Chance(); + +export default Factory.define('user') + .sequence('id') + .attr('firstName', chance.first()) + .attr('lastName', chance.last()) + .attr('username', chance.word({ length: 5 })) + .attr('email', chance.email({ domain: 'example.com' })) + .attr('password', 'Baaa1234!') + .attr('role', 'normal') + .attr( + 'permissions', + JSON.stringify({ + articles: ['read', 'create', 'delete', 'edit'], + comments: ['read', 'create', 'delete', 'edit'], + tags: ['read', 'create', 'delete'], + users: ['read', 'create', 'edit', 'delete'] + }) + ); diff --git a/src/helpers/factory/userTwitter.js b/src/helpers/factory/userTwitter.js new file mode 100644 index 00000000..96ed2290 --- /dev/null +++ b/src/helpers/factory/userTwitter.js @@ -0,0 +1,21 @@ +// eslint-disable-next-line import/no-extraneous-dependencies +import { Factory } from 'rosie'; +import Chance from 'chance'; + +const chance = new Chance(); + +export default Factory.define('user') + .attr('id', `${chance.integer({ min: 100, max: 100000 })}`) + .attr('displayName', chance.name()) + .attr('username', chance.word({ length: 5 })) + .attr('photos', [{ value: 'image.jpg' }]) + .attr('provider', 'twitter') + .attr( + 'permissions', + JSON.stringify({ + articles: ['read', 'create', 'delete', 'edit'], + comments: ['read', 'create', 'delete', 'edit'], + tags: ['read', 'create', 'delete'], + users: ['read', 'create', 'edit', 'delete'] + }) + ); diff --git a/src/helpers/generator/index.js b/src/helpers/generator/index.js new file mode 100644 index 00000000..d62f2a95 --- /dev/null +++ b/src/helpers/generator/index.js @@ -0,0 +1,7 @@ +import slug from './slug'; +import readtime from './readtime'; + +export default { + slug, + readtime +}; diff --git a/src/helpers/generator/readtime.js b/src/helpers/generator/readtime.js new file mode 100644 index 00000000..460ff76f --- /dev/null +++ b/src/helpers/generator/readtime.js @@ -0,0 +1,28 @@ +/* +Estimation of read time inspired by Nick Fisher +Link: https://blog.medium.com/read-time-and-you-bc2048ab620c +*/ +export default (text) => { + const MINUTES = 60; + const WORD_PER_MINUTE = 265; + const SECONDS_PER_IMAGE = 12; + // number of images in the article + const countImages = (text.match(/ 2) { + imageSeconds = SECONDS_PER_IMAGE + 10 + [countImages * 2]; // every additional image take 2sec + } + const numberOfWords = text.split(' ').length; // calculate number of words + // calculate seconds you can read the text + const wordsSeconds = (numberOfWords * MINUTES) / WORD_PER_MINUTE; + const totalSeconds = parseInt((wordsSeconds + imageSeconds) / MINUTES, 0); + return totalSeconds; +}; diff --git a/src/helpers/generator/slug.js b/src/helpers/generator/slug.js new file mode 100644 index 00000000..c4e9e00a --- /dev/null +++ b/src/helpers/generator/slug.js @@ -0,0 +1,12 @@ +import slugify from 'slug'; +import uniqid from 'uniqid'; + +export default (title) => { + if (title.length > 70) title = title.substring(0, 70); + // generate slug; + return `${slugify(title, { + lower: true, + remove: /[.]/g, + symbols: false + })}-${uniqid.process()}`; +}; diff --git a/src/helpers/index.js b/src/helpers/index.js new file mode 100644 index 00000000..3172858e --- /dev/null +++ b/src/helpers/index.js @@ -0,0 +1,33 @@ +import * as factory from './factory'; +import * as validation from './validation'; +import * as token from './tokens'; +import * as password from './password'; +import isUser from './isUser'; +import checkCreateUpdateUserErrors from './checkCreateUpdateUserErrors'; +import parameters from './parameters'; +import clearInvalidToken from './clearInvalidToken'; +import generator from './generator'; +import * as filters from './searchArticleFilters'; +import * as notification from './notifications'; +import sendMail from './mailer'; +import isActiveUser from './isActiveUser'; +import * as urlHelper from './urlHelper'; +import articles from './articles'; + +export { + isUser, + factory, + validation, + password, + sendMail, + checkCreateUpdateUserErrors, + parameters, + clearInvalidToken, + token, + generator, + filters, + notification, + isActiveUser, + urlHelper, + articles +}; diff --git a/src/helpers/isActiveUser.js b/src/helpers/isActiveUser.js new file mode 100644 index 00000000..544f2207 --- /dev/null +++ b/src/helpers/isActiveUser.js @@ -0,0 +1,14 @@ +import { User } from '../queries'; + +/** + * @param {object} input + * @returns {boolean} return true if the user is active or false if not + */ +export default async (input = {}) => { + const checkUser = await User.findOne(input); + if (!checkUser.errors && Object.keys(checkUser).length > 0) { + if (checkUser.isActive) { + return true; + } + } +}; diff --git a/src/helpers/isUser.js b/src/helpers/isUser.js new file mode 100644 index 00000000..be03d0ee --- /dev/null +++ b/src/helpers/isUser.js @@ -0,0 +1,13 @@ +import { User } from '../queries'; + +/** + * @param {object} input + * @returns {boolean} return true if the user exists or false if not + */ +export default async (input) => { + const findUser = await User.findOne(input); + if (!findUser.errors && Object.keys(findUser).length > 0) { + return true; + } + return false; +}; diff --git a/src/helpers/mailer/index.js b/src/helpers/mailer/index.js new file mode 100644 index 00000000..e2fcb1eb --- /dev/null +++ b/src/helpers/mailer/index.js @@ -0,0 +1,3 @@ +import sendMail from './sendMail'; + +export default sendMail; diff --git a/src/helpers/mailer/sendMail.js b/src/helpers/mailer/sendMail.js new file mode 100644 index 00000000..1fbdf401 --- /dev/null +++ b/src/helpers/mailer/sendMail.js @@ -0,0 +1,40 @@ +import dotenv from 'dotenv'; +import mailer from '@sendgrid/mail'; +import * as template from './templates'; + +dotenv.config(); + +export default async (to, action, data) => { + const { SENDGRID_API_KEY, EMAIL_SENDER, NODE_ENV } = process.env; + + mailer.setApiKey(SENDGRID_API_KEY); + + const notifier = template[action](data); + + const message = { + to, + from: EMAIL_SENDER, + subject: notifier.subject, + text: 'Authors Haven', + html: `
+
+
+ Authors Haven - Ninjas +
+
+ ${notifier.html} +
+
+ +
+ Andela, Team @Ninjas - Cohort 4 +
+
+
+ Copyright, 2019
+ Andela, Team Ninjas +
+
` + }; + return NODE_ENV === 'test' ? true : mailer.send(message); +}; diff --git a/src/helpers/mailer/templates/index.js b/src/helpers/mailer/templates/index.js new file mode 100644 index 00000000..ab4cd6f7 --- /dev/null +++ b/src/helpers/mailer/templates/index.js @@ -0,0 +1,7 @@ +import notification from './notification'; +import signup from './signup'; +import resetPassword from './resetPassword'; +import updateEmail from './updateEmail'; + +export { notification, updateEmail }; +export { signup, resetPassword }; diff --git a/src/helpers/mailer/templates/notification.js b/src/helpers/mailer/templates/notification.js new file mode 100644 index 00000000..d55c5a75 --- /dev/null +++ b/src/helpers/mailer/templates/notification.js @@ -0,0 +1,6 @@ +export default (data) => { + const message = {}; + message.subject = 'Authors Haven - Notification'; + message.html = `

${data.message}

`; + return message; +}; diff --git a/src/helpers/mailer/templates/resetPassword.js b/src/helpers/mailer/templates/resetPassword.js new file mode 100644 index 00000000..a4f01344 --- /dev/null +++ b/src/helpers/mailer/templates/resetPassword.js @@ -0,0 +1,18 @@ +import dotenv from 'dotenv'; +import { generate as generateToken } from '../../tokens'; + +dotenv.config(); + +export default (data) => { + const message = {}; + const token = generateToken({ email: data.email }, { expiresIn: '1h' }); + const appUrl = process.env.APP_URL_FRONTEND; + const link = `${appUrl}/reset-password/${token}`; + message.subject = 'Reset your password - Authors Haven'; + message.html = `Hello ${data.names}
, +

You are receiving this because you have requested the reset of the password,
+ Click on the reset link bellow to complete the process
+


+Resert password Now

`; + return message; +}; diff --git a/src/helpers/mailer/templates/signup.js b/src/helpers/mailer/templates/signup.js new file mode 100644 index 00000000..9b7fc33f --- /dev/null +++ b/src/helpers/mailer/templates/signup.js @@ -0,0 +1,27 @@ +import 'dotenv/config'; +import { generate as generateToken } from '../../tokens'; + +export default ({ email, firstName, lastName }) => { + const message = {}; + const token = generateToken({ email }, { expiresIn: '1h' }); + const appUrl = process.env.APP_URL_BACKEND; + const singUpLink = `${appUrl}/api/v1/auth/activate/${token}`; + + message.subject = 'Activate your account - Authors Haven'; + message.html = `Hello ${firstName} ${lastName}
, +

+ You are receiving this because you requested to create an account on Authors Haven, +
+ Please, click on the link bellow to activate your account!!! +


+ + Activate account Now + +

`; + + return message; +}; diff --git a/src/helpers/mailer/templates/updateEmail.js b/src/helpers/mailer/templates/updateEmail.js new file mode 100644 index 00000000..7be1864e --- /dev/null +++ b/src/helpers/mailer/templates/updateEmail.js @@ -0,0 +1,18 @@ +import dotenv from 'dotenv'; +import { generate as generateToken } from '../../tokens'; + +dotenv.config(); + +export default (data) => { + const message = {}; + const token = generateToken({ userId: data.userId, email: data.email }, { expiresIn: '1h' }); + const appUrl = process.env.APP_URL_BACKEND; + const link = `${appUrl}/api/v1/users/email/confirm/${token}`; + message.subject = 'Update email - Authors Haven'; + message.html = `Hello
, +

You are receiving this because you (or someone else) have requested the reset of your current email,
+ Click on the link bellow to confirm the process
+


+ Confirm email

`; + return message; +}; diff --git a/src/helpers/notifications/commentArticle.js b/src/helpers/notifications/commentArticle.js new file mode 100644 index 00000000..d901a67a --- /dev/null +++ b/src/helpers/notifications/commentArticle.js @@ -0,0 +1,50 @@ +import dotenv from 'dotenv'; +import sendNotification from './sendNotification'; +import { User, Article } from '../../queries'; + +dotenv.config(); + +export default async (comment) => { + try { + const { APP_URL_FRONTEND } = process.env; + const author = await User.findOne({ id: comment.userId }); + let favorites = await Article.favorite.getAll(null, comment.articleSlug); + + favorites = !favorites.errors ? favorites.map(favorite => favorite.favoritedBy) : null; + + return favorites.map(async (favoritedBy) => { + const url = `${APP_URL_FRONTEND}/articles/${comment.articleSlug}`; + + const inAppMessage = `Hello ${favoritedBy.firstName} ${favoritedBy.lastName}, ${ + author.firstName + } ${author.lastName} commented on a article you favorite`; + + const emailMessage = ` + Hello ${favoritedBy.firstName} ${favoritedBy.lastName}, +
+ ${author.firstName} ${author.lastName} commented on a article you favorite +
+ Please, click on the link bellow to read the comment +


+ + Read Now + `; + + const data = { + resource: 'articles', + action: 'comment', + user: favoritedBy, + inAppMessage, + emailMessage, + url + }; + + await sendNotification(data); + }); + } catch (error) { + return { errors: error }; + } +}; diff --git a/src/helpers/notifications/getUserConfig.js b/src/helpers/notifications/getUserConfig.js new file mode 100644 index 00000000..6294feb9 --- /dev/null +++ b/src/helpers/notifications/getUserConfig.js @@ -0,0 +1,12 @@ +import { Notification } from '../../queries'; + +/** + * @param {integer} userId + * @returns {object} the config token + */ +export default async (userId) => { + const userConfig = await Notification.config.getOne(userId); + return !userConfig.errors && Object.keys(userConfig).length + ? { config: JSON.parse(userConfig.config) } + : null; +}; diff --git a/src/helpers/notifications/index.js b/src/helpers/notifications/index.js new file mode 100644 index 00000000..ddc22397 --- /dev/null +++ b/src/helpers/notifications/index.js @@ -0,0 +1,8 @@ +import config from './getUserConfig'; +import publishArticle from './publishArticle'; +import commentArticle from './commentArticle'; +import send from './sendNotification'; + +export { + config, publishArticle, commentArticle, send +}; diff --git a/src/helpers/notifications/publishArticle.js b/src/helpers/notifications/publishArticle.js new file mode 100644 index 00000000..81df4a05 --- /dev/null +++ b/src/helpers/notifications/publishArticle.js @@ -0,0 +1,44 @@ +import dotenv from 'dotenv'; +import sendNotification from './sendNotification'; +import { User } from '../../queries'; + +dotenv.config(); + +export default async (authorId, articleSlug) => { + try { + const { APP_URL_FRONTEND } = process.env; + const author = await User.findOne({ id: authorId }); + let followers = await User.follow.getAll({ followed: authorId }); + followers = followers.length ? followers.map(follower => follower.get().follower) : []; + + return followers.map(async (follower) => { + const url = `${APP_URL_FRONTEND}/articles/${articleSlug}`; + + const inAppMessage = `Hello ${follower.firstName} ${follower.lastName}, ${author.firstName} ${ + author.lastName + } published a new article`; + + const emailMessage = ` + Hello ${follower.firstName} ${follower.lastName}, +
+ ${author.firstName} ${author.lastName} published a new article +
+ Please, click on the link bellow to read this article +


+ Read Now

`; + + const data = { + resource: 'articles', + action: 'publish', + user: follower, + inAppMessage, + emailMessage, + url + }; + + await sendNotification(data); + }); + } catch (error) { + return { errors: error }; + } +}; diff --git a/src/helpers/notifications/sendNotification.js b/src/helpers/notifications/sendNotification.js new file mode 100644 index 00000000..b9d0abd2 --- /dev/null +++ b/src/helpers/notifications/sendNotification.js @@ -0,0 +1,30 @@ +import { Notification } from '../../queries'; +import getUserConfig from './getUserConfig'; +import sendMail from '../mailer'; + +/** + * @param {object} data + * @returns {object} notification + */ +export default async (data) => { + let inAppNotification = {}; + let emailNotification = {}; + const { + resource, action, user, inAppMessage, emailMessage, url + } = data; + + const userConfig = await getUserConfig(user.id); + if (userConfig && !userConfig.errors) { + const { inApp, email } = userConfig.config; + if (inApp[resource].show && inApp[resource].on.includes(action)) { + inAppNotification = await Notification.create(user.id, inAppMessage, 'inApp', url); + } + + if (user.email && email[resource].show && email[resource].on.includes(action)) { + emailNotification = (await Notification.create(user.id, inAppMessage, 'email', url)) + && (await sendMail(user.email, 'notification', { message: emailMessage })); + } + } + + return { inAppNotification, emailNotification }; +}; diff --git a/src/helpers/parameters/checkDuplication.js b/src/helpers/parameters/checkDuplication.js new file mode 100644 index 00000000..4d57b78d --- /dev/null +++ b/src/helpers/parameters/checkDuplication.js @@ -0,0 +1,15 @@ +/** + * @param {string} query queries to be evaluated + * @param {string} required + * @returns {array} an array of errors or an empty array if no error + */ +export default (query) => { + const errors = {}; + Object.keys(query).forEach((key) => { + if (Array.isArray(query[key])) { + errors[key] = `${key} can not be declared more than once`; + } + }); + + return errors; +}; diff --git a/src/helpers/parameters/index.js b/src/helpers/parameters/index.js new file mode 100644 index 00000000..63999b5f --- /dev/null +++ b/src/helpers/parameters/index.js @@ -0,0 +1,4 @@ +import duplication from './checkDuplication'; + +// eslint-disable-next-line import/prefer-default-export +export default { duplication }; diff --git a/src/helpers/passport.js b/src/helpers/passport.js new file mode 100644 index 00000000..0e93c950 --- /dev/null +++ b/src/helpers/passport.js @@ -0,0 +1,13 @@ +import { Strategy as JWTStrategy, ExtractJwt } from 'passport-jwt'; +import dotenv from 'dotenv'; +import Passport from '../config/passportLocalConfig'; + +dotenv.config(); +const opts = {}; +opts.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken(); +opts.secretOrKey = process.env.SECRET_KEY; + + +export default (passport) => { + passport.use('jwt', new JWTStrategy(opts, Passport)); +}; diff --git a/src/helpers/password/comparePassword.js b/src/helpers/password/comparePassword.js new file mode 100644 index 00000000..3d842e1d --- /dev/null +++ b/src/helpers/password/comparePassword.js @@ -0,0 +1,3 @@ +import bcrypt from 'bcryptjs'; + +export default (password, hashedPassword) => bcrypt.compareSync(password, hashedPassword); diff --git a/src/helpers/password/hashPassword.js b/src/helpers/password/hashPassword.js new file mode 100644 index 00000000..cc92a983 --- /dev/null +++ b/src/helpers/password/hashPassword.js @@ -0,0 +1,3 @@ +import bcrypt from 'bcryptjs'; + +export default password => bcrypt.hashSync(password, bcrypt.genSaltSync(8)); diff --git a/src/helpers/password/index.js b/src/helpers/password/index.js new file mode 100644 index 00000000..71498e5c --- /dev/null +++ b/src/helpers/password/index.js @@ -0,0 +1,5 @@ +/* eslint-disable require-jsdoc */ +import hash from './hashPassword'; +import compare from './comparePassword'; + +export { hash, compare }; diff --git a/src/helpers/queryHelper.js b/src/helpers/queryHelper.js new file mode 100644 index 00000000..dbfc2f7e --- /dev/null +++ b/src/helpers/queryHelper.js @@ -0,0 +1,27 @@ +const dbFindSingle = async (model, whereCondition = {}, include) => model.findOne({ + where: whereCondition, + logging: false, + include +}); + +const dbFindAll = async (model, whereCondition, offset = 0, limit = 20, include) => model.findAll({ + offset, + limit, + where: whereCondition, + logging: false, + include +}); + +const dbCreate = async (model, condition) => model.create(condition, { logging: false }); + +const dbDelete = async (model, whereCondition) => model.destroy({ + where: whereCondition, + logging: false +}); +const dbUpdate = async (model, condition, whereCondition = {}) => { + model.update(condition, { where: whereCondition, logging: false }); +}; + +export { + dbFindSingle, dbFindAll, dbCreate, dbDelete, dbUpdate +}; diff --git a/src/helpers/searchArticleFilters/filterQueryBuilder.js b/src/helpers/searchArticleFilters/filterQueryBuilder.js new file mode 100644 index 00000000..265b7216 --- /dev/null +++ b/src/helpers/searchArticleFilters/filterQueryBuilder.js @@ -0,0 +1,46 @@ +import db from '../../models'; + +export default (condition = {}) => { + let where = { status: { [db.Op.notIn]: ['deleted', 'draft'] } }; + if (condition.author !== undefined) { + where = { + ...where, + [db.Op.and]: { + [db.Op.or]: [ + { + '$author.username$': { [db.Op.iLike]: `${condition.author}` } + }, + { + '$author.firstName$': { [db.Op.iLike]: `${condition.author}` } + }, + { + '$author.lastName$': { [db.Op.iLike]: `${condition.author}` } + } + ] + } + }; + } + if (condition.tag !== undefined) { + where = { + ...where, + [db.Op.and]: { + tagList: { + [db.Op.contains]: [`${condition.tag}`] + } + } + }; + } + if (condition.keyword !== undefined) { + where = { + ...where, + [db.Op.and]: [ + { + title: { + [db.Op.iLike]: `%${condition.keyword}%` + } + } + ] + }; + } + return where; +}; diff --git a/src/helpers/searchArticleFilters/index.js b/src/helpers/searchArticleFilters/index.js new file mode 100644 index 00000000..51abd593 --- /dev/null +++ b/src/helpers/searchArticleFilters/index.js @@ -0,0 +1,4 @@ +import filterQueryBuilder from './filterQueryBuilder'; + +// eslint-disable-next-line import/prefer-default-export +export { filterQueryBuilder }; diff --git a/src/helpers/socketIO.js b/src/helpers/socketIO.js new file mode 100644 index 00000000..19538390 --- /dev/null +++ b/src/helpers/socketIO.js @@ -0,0 +1,45 @@ +import socket from 'socket.io'; +import * as tokenHelper from './tokens'; +import { Chat, User } from '../queries'; + +export default (server) => { + const io = socket(server); + io.sockets.on('connection', (sock) => { + sock.on('connectedToChat', async () => { + let chats = []; + const savedChats = await Chat.getAll(); + + if (!savedChats.errors) { + savedChats.forEach((savedChat) => { + const user = savedChat.get().User.get(); + delete savedChat.dataValues.User; + const chat = { ...savedChat.get(), user }; + chats = [...chats, chat]; + }); + sock.emit('allMessages', chats); + } + }); + + sock.on('deleteChat', async (chatId, userId) => { + const deleteChat = Chat.remove(chatId, userId); + + if (!deleteChat.errors) { + sock.emit('chatDeleted', chatId, userId); + sock.broadcast.emit('chatDeleted', chatId, userId); + } + }); + + sock.on('message', async (data) => { + const { message, token } = data; + const { id: userId } = tokenHelper.decode(token); + const created = (userId && (await Chat.save(userId, message))) || null; + if (created) { + const findUser = await User.findOne({ id: userId }); + delete findUser.password; + + sock.emit('newMessage', { ...created, user: findUser }); + sock.broadcast.emit('newMessage', { ...created, user: findUser }); + } + }); + }); +}; diff --git a/src/helpers/tokens/index.js b/src/helpers/tokens/index.js new file mode 100644 index 00000000..80bc14e1 --- /dev/null +++ b/src/helpers/tokens/index.js @@ -0,0 +1,4 @@ +import generate from './tokenGenerator'; +import decode from './tokenDecoder'; + +export { generate, decode }; diff --git a/src/helpers/tokens/tokenDecoder.js b/src/helpers/tokens/tokenDecoder.js new file mode 100644 index 00000000..fa3fed44 --- /dev/null +++ b/src/helpers/tokens/tokenDecoder.js @@ -0,0 +1,18 @@ +import jwt from 'jsonwebtoken'; +import dotenv from 'dotenv'; + +dotenv.config(); + +/** + * @param {string} token the token to decode + * @returns {object} the decoded token + */ +export default (token = '') => { + try { + return jwt.verify(token, process.env.SECRET_KEY); + } catch (error) { + return { + errors: error + }; + } +}; diff --git a/src/helpers/tokens/tokenGenerator.js b/src/helpers/tokens/tokenGenerator.js new file mode 100644 index 00000000..031028a6 --- /dev/null +++ b/src/helpers/tokens/tokenGenerator.js @@ -0,0 +1,23 @@ +import jwt from 'jsonwebtoken'; +import dotenv from 'dotenv'; + +dotenv.config(); + +/** + * @param {object} payload the payload to encode the token + * @param {object} expiresIn the expiration time of the token + * @returns {string} the generated token + */ +export default (payload = {}, expiresIn = { expiresIn: '1d' }) => { + let isValidPayload = true; + + if (typeof payload === 'number') { + isValidPayload = false; + } else if (payload === null) { + isValidPayload = false; + } else if (typeof payload === 'object' && !Object.keys(payload).length) { + isValidPayload = false; + } + + return isValidPayload ? jwt.sign(payload, process.env.SECRET_KEY, expiresIn) : null; +}; diff --git a/src/helpers/urlHelper.js b/src/helpers/urlHelper.js new file mode 100644 index 00000000..3012506f --- /dev/null +++ b/src/helpers/urlHelper.js @@ -0,0 +1,15 @@ +import 'dotenv/config'; + +const { APP_URL_BACKEND, APP_URL_FRONTEND } = process.env; + +const frontend = { + appUrl: APP_URL_FRONTEND, + travis: 'https://travis-ci.com' +}; + +const backend = { + appUrl: APP_URL_BACKEND, + travis: 'https://travis-ci.com' +}; + +export { frontend, backend }; diff --git a/src/helpers/validation/articleSlug.js b/src/helpers/validation/articleSlug.js new file mode 100644 index 00000000..64d2618b --- /dev/null +++ b/src/helpers/validation/articleSlug.js @@ -0,0 +1,12 @@ +import Joi from '@hapi/joi'; + +export default (slug) => { + const schema = Joi.object().keys({ + slug: Joi.string() + .min(16) + .max(85) + .required() + }); + + return Joi.validate(slug, schema, { abortEarly: false }); +}; diff --git a/src/helpers/validation/comment.js b/src/helpers/validation/comment.js new file mode 100644 index 00000000..18274929 --- /dev/null +++ b/src/helpers/validation/comment.js @@ -0,0 +1,14 @@ +import Joi from '@hapi/joi'; +import commentErrors from './commentErrors'; + +export default (comment) => { + const schema = { + body: Joi.string() + .min(3) + .required() + .trim() + .error(commentErrors) + }; + + return Joi.validate(comment, schema); +}; diff --git a/src/helpers/validation/commentErrors.js b/src/helpers/validation/commentErrors.js new file mode 100644 index 00000000..c2ec6b06 --- /dev/null +++ b/src/helpers/validation/commentErrors.js @@ -0,0 +1,15 @@ +export default (errors) => { + errors.forEach((err) => { + switch (err.type) { + case 'any.empty': + err.message = " Comment can't be empty"; + break; + case 'string.min': + err.message = ' Your comment should have at least 3 characters!'; + break; + default: + break; + } + }); + return errors; +}; diff --git a/src/helpers/validation/createArticle.js b/src/helpers/validation/createArticle.js new file mode 100644 index 00000000..078b1bdf --- /dev/null +++ b/src/helpers/validation/createArticle.js @@ -0,0 +1,25 @@ +import Joi from 'joi'; + +export default (input) => { + const schema = Joi.object().keys({ + title: Joi.string() + .min(5) + .max(255) + .required(), + description: Joi.string() + .min(5) + .max(255) + .required(), + body: Joi.string() + .min(5) + .required(), + coverUrl: Joi.any().optional(), + tagList: Joi.array() + .min(2) + .max(255) + .optional(), + userId: Joi.number().optional() + }); + + return Joi.validate(input, schema, { abortEarly: false }); +}; diff --git a/src/helpers/validation/createHighlight.js b/src/helpers/validation/createHighlight.js new file mode 100644 index 00000000..35ba98e3 --- /dev/null +++ b/src/helpers/validation/createHighlight.js @@ -0,0 +1,19 @@ +import Joi from 'joi'; + +export default (input) => { + const schema = Joi.object().keys({ + highlightedText: Joi.string() + .min(5) + .required(), + startIndex: Joi.number().required(), + stopIndex: Joi.number().required(), + comment: Joi.string() + .allow(null, '') + .optional(), + anchorKey: Joi.string() + .allow(null, '') + .optional() + }); + + return Joi.validate(input, schema, { abortEarly: false }); +}; diff --git a/src/helpers/validation/createPermissions.js b/src/helpers/validation/createPermissions.js new file mode 100644 index 00000000..cac99715 --- /dev/null +++ b/src/helpers/validation/createPermissions.js @@ -0,0 +1,18 @@ +import Joi from 'joi'; + +export default (input) => { + const schema = Joi.object().keys({ + userType: Joi.string() + .min(5) + .max(6) + .required(), + permissions: Joi.object({ + articles: Joi.array().required(), + comments: Joi.array().required(), + tags: Joi.array().required(), + users: Joi.array().required() + }).required() + }); + + return Joi.validate(input, schema, { abortEarly: false }); +}; diff --git a/src/helpers/validation/createTag.js b/src/helpers/validation/createTag.js new file mode 100644 index 00000000..061d49dd --- /dev/null +++ b/src/helpers/validation/createTag.js @@ -0,0 +1,18 @@ +import Joi from 'joi'; + +export default (input) => { + const schema = Joi.object().keys({ + tagList: Joi.array() + .min(1) + .max(5) + .items( + Joi.string() + .min(2) + .max(25) + .label('Tag element') + ) + .required() + }); + + return Joi.validate(input, schema, { abortEarly: false }); +}; diff --git a/src/helpers/validation/email.js b/src/helpers/validation/email.js new file mode 100644 index 00000000..cefcba43 --- /dev/null +++ b/src/helpers/validation/email.js @@ -0,0 +1,32 @@ +/** + * @param {string} input + * @param {string} required + * @returns {array} an array of errors or an empty array if no error + */ +export default (input, required = '') => { + if (!input && !required) { + return []; + } + + let errors = []; + const re = /[A-Z0-9]+@[A-Z0-9-]+\.[A-Z]{2,4}/gim; + const reUsername = /[\\ ,;:"!#$%&'*+/=?^`{|}~([\])]/g; + const reDomainSLD = /[ \\,;:"!#$%&'*+/=?^`{|}~([\])]/g; + const reDomainTLD = /[\d+ \\,;:"!#$%&'*+-/=?^_`{|}~([\])]/g; + + const emailUsername = input.substring(0, input.lastIndexOf('@')); + const emailDomainSLD = input.substring(input.lastIndexOf('@') + 1, input.lastIndexOf('.')); + const emailDomainTLD = input.substring(input.lastIndexOf('.') + 1); + + const isEmailUsername = !emailUsername.match(/^[0-9]/) && !reUsername.test(emailUsername); + const isEmailDomainSLD = !emailDomainSLD.match(/^[0-9]/) && !reDomainSLD.test(emailDomainSLD); + const isEmailDomainTLD = !emailDomainTLD.match(/^[0-9]/) && !reDomainTLD.test(emailDomainTLD); + + if (re.test(input) && isEmailUsername && isEmailDomainSLD && isEmailDomainTLD) { + return []; + } + + errors = [...errors, 'Please provide a valid email address']; + + return errors; +}; diff --git a/src/helpers/validation/index.js b/src/helpers/validation/index.js new file mode 100644 index 00000000..f0d6a595 --- /dev/null +++ b/src/helpers/validation/index.js @@ -0,0 +1,29 @@ +import email from './email'; +import password from './password'; +import createArticle from './createArticle'; +import updateArticle from './updateArticle'; +import newUser from './newUser'; +import createTag from './createTag'; +import queryParameters from './queryParameters'; +import updateUser from './updateUser'; +import report from './report'; +import createHighlight from './createHighlight'; +import createPermissions from './createPermissions'; +import notificationConfig from './notificationConfig'; +import name from './name'; +import articleSlug from './articleSlug'; + +export { email, password, name }; +export { + createArticle, + updateArticle, + createTag, + newUser, + queryParameters, + updateUser, + report, + createHighlight, + createPermissions, + notificationConfig, + articleSlug +}; diff --git a/src/helpers/validation/name.js b/src/helpers/validation/name.js new file mode 100644 index 00000000..62daaeab --- /dev/null +++ b/src/helpers/validation/name.js @@ -0,0 +1,17 @@ +/** + * @param {string} input + * @param {string} required + * @param {string} label + * @returns {boolean|string} true if the name is valid otherwise returns an error message + */ +export default (input, required = '', label) => { + const re = /[A-Z]{2,4}/gim; + const reName = /[0-9\\ ,;:"!#$%&'*+/=?^`{|}~([\])]/g; + + return ( + (!input && !required) + || (re.test(input) && !reName.test(input)) + || (label && `Please provide a valid ${label}, it should only contain alphabetic characters`) + || 'Please provide a valid name, it should only contain alphabetic characters' + ); +}; diff --git a/src/helpers/validation/newUser.js b/src/helpers/validation/newUser.js new file mode 100644 index 00000000..e1075229 --- /dev/null +++ b/src/helpers/validation/newUser.js @@ -0,0 +1,38 @@ +import Joi from 'joi'; + +export default (input) => { + const schema = Joi.object().keys({ + firstName: Joi.string() + .min(2) + .max(45) + .required() + .label('First name'), + lastName: Joi.string() + .min(2) + .max(45) + .required() + .label('Last name'), + username: Joi.string() + .min(4) + .max(45) + .required(), + email: Joi.string() + .min(5) + .max(100) + .required(), + bio: Joi.string() + .min(5) + .optional(), + password: Joi.string() + .min(8) + .max(100) + .required(), + role: Joi.string() + .min(2) + .max(100) + .optional(), + permissions: Joi.object().optional() + }); + + return Joi.validate(input, schema, { abortEarly: false }); +}; diff --git a/src/helpers/validation/notificationConfig.js b/src/helpers/validation/notificationConfig.js new file mode 100644 index 00000000..f01c6964 --- /dev/null +++ b/src/helpers/validation/notificationConfig.js @@ -0,0 +1,27 @@ +import Joi from 'joi'; + +export default (input) => { + const configBody = () => Joi.object() + .keys({ + articles: Joi.object() + .keys({ + show: Joi.boolean().required(), + on: Joi.array() + .items(Joi.string().min(3)) + .required() + }) + .optional() + }) + .required(); + + const schema = Joi.object().keys({ + config: Joi.object() + .keys({ + inApp: configBody(), + email: configBody() + }) + .required() + }); + + return Joi.validate(input, schema, { abortEarly: false }); +}; diff --git a/src/helpers/validation/password.js b/src/helpers/validation/password.js new file mode 100644 index 00000000..5fc25e5c --- /dev/null +++ b/src/helpers/validation/password.js @@ -0,0 +1,25 @@ +/** + * @param {string} input + * @param {string} required + * @returns {array} an array of errors or an empty array if no error + */ +export default (input, required = '') => { + let errors = []; + + if (!input && !required) { + return []; + } + if ( + input.match(/^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,25}$/) + && input.match(/[ \\,;:"!#$@*%&'+-/=?^_`{|}~]/) + ) { + return []; + } + + errors = [ + ...errors, + 'Your password should have a minimum 8 and maximum of 25 characters, it must include at least one upper case letter, one lower case letter, one numeric digit and one special character (*&^!%$@#)' + ]; + + return errors; +}; diff --git a/src/helpers/validation/queryParameters.js b/src/helpers/validation/queryParameters.js new file mode 100644 index 00000000..0e8eca6f --- /dev/null +++ b/src/helpers/validation/queryParameters.js @@ -0,0 +1,30 @@ +import Joi from 'joi'; + +export default (input) => { + const schema = Joi.object().keys({ + limit: Joi.number() + .integer() + .min(1) + .optional(), + offset: Joi.number() + .integer() + .min(0) + .optional(), + keyword: Joi.string() + .min(1) + .max(30) + .optional(), + author: Joi.string() + .min(2) + .max(50) + .optional(), + tag: Joi.string() + .regex(/^[a-zA-Z]+$/) + .label('Tag') + .min(2) + .max(60) + .optional() + }); + + return Joi.validate(input, schema, { abortEarly: false }); +}; diff --git a/src/helpers/validation/report.js b/src/helpers/validation/report.js new file mode 100644 index 00000000..ebbe8f6d --- /dev/null +++ b/src/helpers/validation/report.js @@ -0,0 +1,17 @@ +import Joi from '@hapi/joi'; + +export default (report) => { + const schema = Joi.object().keys({ + title: Joi.string() + .min(2) + .max(100) + .required(), + body: Joi.string() + .min(2) + .max(300) + .required(), + type: Joi.array().items(Joi.string().required()) + }); + + return Joi.validate(report, schema, { abortEarly: false }); +}; diff --git a/src/helpers/validation/updateArticle.js b/src/helpers/validation/updateArticle.js new file mode 100644 index 00000000..cf520777 --- /dev/null +++ b/src/helpers/validation/updateArticle.js @@ -0,0 +1,20 @@ +import Joi from 'joi'; + +export default (input) => { + const schema = Joi.object().keys({ + title: Joi.string() + .min(5) + .max(255) + .optional(), + description: Joi.string() + .min(5) + .max(255) + .optional(), + body: Joi.string() + .min(5) + .optional(), + coverUrl: Joi.any().optional() + }); + + return Joi.validate(input, schema, { abortEarly: false }); +}; diff --git a/src/helpers/validation/updateUser.js b/src/helpers/validation/updateUser.js new file mode 100644 index 00000000..150f2a61 --- /dev/null +++ b/src/helpers/validation/updateUser.js @@ -0,0 +1,43 @@ +import Joi from 'joi'; + +export default (input) => { + const schema = Joi.object().keys({ + firstName: Joi.string() + .min(2) + .max(45) + .optional() + .label('First name'), + lastName: Joi.string() + .min(2) + .max(45) + .optional() + .label('Last name'), + username: Joi.string() + .min(4) + .max(45) + .optional(), + email: Joi.string() + .min(5) + .max(100) + .optional(), + password: Joi.string() + .min(8) + .max(100) + .optional(), + bio: Joi.string() + .min(5) + .optional(), + image: Joi.string() + .min(5) + .optional(), + role: Joi.string() + .min(2) + .max(10) + .regex(/^[a-zA-Z]{2,10}$/) + .optional(), + permissions: Joi.object().optional(), + isActive: Joi.boolean().optional() + }); + + return Joi.validate(input, schema, { abortEarly: false }); +}; diff --git a/src/index.js b/src/index.js new file mode 100644 index 00000000..61dbb4f7 --- /dev/null +++ b/src/index.js @@ -0,0 +1,65 @@ +import app from './app'; +import socketIO from './helpers/socketIO'; +/** + * Normalize a port into a number, string, or false. + * @param {int} val The port number. + * @returns {int} The port number. + */ +function normalizePort(val) { + const port = parseInt(val, 10); + + if (Number.isNaN(port)) { + return val; // named pipe + } + + if (port >= 0) { + return port; // port number + } + + return false; +} + +/** + * Get port from environment and store in Express. + */ +const port = normalizePort(process.env.PORT || '3000'); +app.set('port', port); + +/** + * Event listener for HTTP server "error" event. + * Normalize a port into a number, string, or false. + * @param {int} error The created error. + * @returns {string} The error message. + */ +function onError(error) { + if (error.syscall !== 'listen') { + throw error; + } + + const bind = typeof port === 'string' ? `Pipe ${port}` : `Port ${port}`; + + // handle specific listen errors with friendly messages + switch (error.code) { + case 'EACCES': + process.stdout.write(`${bind} requires elevated privileges\n`); + process.exit(1); + break; + case 'EADDRINUSE': + process.stdout.write(`${bind} is already in use\n`); + process.exit(1); + break; + default: + throw error; + } +} + +/** + * Listen on provided port, on all network interfaces. + */ +const server = app.server.listen(port, () => { + process.stdout.write(`Server is running on port: ${port}\n`); +}); + +socketIO(server); + +app.server.on('error', onError); diff --git a/src/middlewares/asyncHandler.js b/src/middlewares/asyncHandler.js new file mode 100644 index 00000000..fbb1bc3f --- /dev/null +++ b/src/middlewares/asyncHandler.js @@ -0,0 +1,11 @@ +import status from '../config/status'; + +const asyncHandler = controller => async (req, res, next) => { + try { + await controller(req, res, next); + } catch (err) { + return res.status(status.SERVER_ERROR).json({ errors: { message: err.message } }); + } +}; + +export default asyncHandler; diff --git a/src/middlewares/checkArticle.js b/src/middlewares/checkArticle.js new file mode 100644 index 00000000..8a70d2ad --- /dev/null +++ b/src/middlewares/checkArticle.js @@ -0,0 +1,24 @@ +/* eslint-disable import/named */ + +import status from '../config/status'; +import * as article from '../queries/articles'; + +// eslint-disable-next-line valid-jsdoc +/** + * middleware function used in create comment controller to make check if the article exists + * @param { object } req the request from the user + * @param { object } res The response from the server + * @param { function } next return object + */ +const checkArticle = async (req, res, next) => { + const findArticle = await article.get({ + slug: req.params.articleSlug + }); + if (!findArticle) { + return res.status(status.NOT_FOUND).send({ + message: 'This article does not exist' + }); + } + next(); +}; +export default checkArticle; diff --git a/src/middlewares/checkArticleBySlug.js b/src/middlewares/checkArticleBySlug.js new file mode 100644 index 00000000..800d3183 --- /dev/null +++ b/src/middlewares/checkArticleBySlug.js @@ -0,0 +1,18 @@ +import status from '../config/status'; +import { Article } from '../queries'; +/** + * @param {object} req Request sent to the route + * @param {object} res Response from server + * @param {object} next Allow app to continue + * @returns {object} Object representing the response returned + */ +export default async (req, res, next) => { + const response = await Article.get({ slug: req.params.slug }); + if (!response) { + return res.status(status.NOT_FOUND).send({ + error: 'No article found' + }); + } + req.article = response.get(); + next(); +}; diff --git a/src/middlewares/checkArticleLike.js b/src/middlewares/checkArticleLike.js new file mode 100644 index 00000000..d3d7a37f --- /dev/null +++ b/src/middlewares/checkArticleLike.js @@ -0,0 +1,32 @@ +import * as article from '../queries/articles/likes'; +import statusCode from '../config/status'; +import updateArticleLikes from '../queries/articles/updateArticleLikes'; + +const checkArticleLike = async (req, res, next) => { + const userId = req.user.id; + const { articleSlug } = req.params; + const like = { userId, articleSlug }; + let message = 'You deleted your reaction'; + + const findLike = await article.getSingleLike(like); + if (!findLike) { + return next(); + } + if (req.params.status === 'like' && findLike.status === 'dislike') { + await article.updateLike({ status: req.params.status }, like); + await updateArticleLikes(req); + return res + .status(statusCode.OK) + .json({ message: 'You liked the article', createLike: { userId } }); + } + if (req.params.status === 'dislike' && findLike.status === 'like') { + await article.updateLike({ status: req.params.status }, like); + await updateArticleLikes(req); + message = 'You disliked the article'; + return res.status(statusCode.OK).json({ message, createLike: { userId } }); + } + await article.deleteLike(like); + await updateArticleLikes(req); + return res.status(statusCode.OK).json({ message, createLike: { userId } }); +}; +export default checkArticleLike; diff --git a/src/middlewares/checkArticlePermissions.js b/src/middlewares/checkArticlePermissions.js new file mode 100644 index 00000000..c741ab81 --- /dev/null +++ b/src/middlewares/checkArticlePermissions.js @@ -0,0 +1,12 @@ +import status from '../config/status'; + +const checkArticlePermissions = role => (req, res, next) => ((role[req.user.role] === 'self' && req.user.id === req.article.userId) + || role[req.user.role] === 'all' + ? next() + : res.status(status.UNAUTHORIZED).json({ + errors: { + permission: "you don't have the required permission to perform this action" + } + })); + +export default checkArticlePermissions; diff --git a/src/middlewares/checkComment.js b/src/middlewares/checkComment.js new file mode 100644 index 00000000..091fbd2e --- /dev/null +++ b/src/middlewares/checkComment.js @@ -0,0 +1,17 @@ +/* eslint-disable import/named */ +import status from '../config/status'; +import * as Comment from '../queries/comments'; +// eslint-disable-next-line valid-jsdoc +/** + * @param { object } req the request. + * @param { object } res The response. + * @param { function } next return object + */ +export default async function checkComment(req, res, next) { + const { articleSlug, commentId } = req.params; + const comment = await Comment.getSingle({ id: commentId, articleSlug }); + if (!comment) { + return res.status(status.NOT_FOUND).json({ errors: { message: 'Comment not found' } }); + } + next(); +} diff --git a/src/middlewares/checkCommentLike.js b/src/middlewares/checkCommentLike.js new file mode 100644 index 00000000..d3a2b28d --- /dev/null +++ b/src/middlewares/checkCommentLike.js @@ -0,0 +1,23 @@ +import statusCode from '../config/status'; +import * as comment from '../queries/comments/likes'; +import updateCommentLikes from '../queries/comments/updateCommentLikes'; + +const checkArticleLike = async (req, res, next) => { + const userId = req.user.id; + const { commentId, articleSlug } = req.params; + const like = { userId, commentId, articleSlug }; + const message = 'You deleted your reaction'; + const findLike = await comment.getSingleLike(like); + + if (!findLike) { + next(); + return true; + } + await comment.deleteLike(like); + await updateCommentLikes(req); + res.status(statusCode.OK).json({ + message, + createLike: findLike + }); +}; +export default checkArticleLike; diff --git a/src/middlewares/checkPermissions.js b/src/middlewares/checkPermissions.js new file mode 100644 index 00000000..5d292aa6 --- /dev/null +++ b/src/middlewares/checkPermissions.js @@ -0,0 +1,24 @@ +import status from '../config/status'; + +export default (Object.checkPermissions = permission => (req, res, next) => { + const permissions = JSON.parse(req.user.permissions); + + if (!permissions[permission.route]) { + return res.status(status.BAD_REQUEST).json({ + errors: { + permission: "Sorry, the resource you try to access doesn't exist" + } + }); + } + + if (permissions[permission.route].includes(permission.action)) { + return next(); + } + + return res.status(status.UNAUTHORIZED).json({ + errors: { + permission: + permission.message || "sorry, you don't have the required permission to access this route" + } + }); +}); diff --git a/src/middlewares/checkReport.js b/src/middlewares/checkReport.js new file mode 100644 index 00000000..b018d57a --- /dev/null +++ b/src/middlewares/checkReport.js @@ -0,0 +1,33 @@ +import status from '../config/status'; +import * as report from '../queries/reports'; +import * as validate from '../helpers/validation'; +import Error from '../helpers/errorHandler'; + +const validateReport = (req, res, next) => { + const result = validate.report(req.body); + if (result.error) { + return Error.joiErrorHandler(res, result); + } + next(); +}; +const checkUserReport = async (req, res, next) => { + const userId = req.user.id; + const { articleSlug } = req.params; + const newreport = { userId, articleSlug }; + const findReport = await report.getSingle(newreport); + if (findReport) { + return res.status(status.EXIST).json({ message: 'You already reported the article' }); + } + next(); +}; +const checkReportExist = async (req, res, next) => { + const { articleSlug, reportId } = req.params; + const newreport = { id: reportId, articleSlug }; + const findReport = await report.getSingle(newreport); + if (!findReport) { + return res.status(status.BAD_REQUEST).json({ message: 'The report does not exist' }); + } + next(); +}; + +export { checkUserReport, checkReportExist, validateReport }; diff --git a/src/middlewares/checkSignUpPermission.js b/src/middlewares/checkSignUpPermission.js new file mode 100644 index 00000000..e90b666e --- /dev/null +++ b/src/middlewares/checkSignUpPermission.js @@ -0,0 +1,25 @@ +import status from '../config/status'; +import getPermissions from '../config/permissions'; + +export default async (req, res, next) => { + let isAllowed = true; + [req.user, req.body] = [req.user || {}, req.body || {}]; + const role = req.user.role || null; + const defaultPermissions = (await getPermissions()).normal; + const permissions = req.body.permissions && JSON.stringify(req.body.permissions); + + if (!role || role === 'normal') { + if (req.body.role || req.body.permissions) { + isAllowed = false; + } + } + req.user.permissions = defaultPermissions; // social media sign up permissions + req.body.permissions = permissions || defaultPermissions; // local sign up + return isAllowed + ? next() + : res.status(status.UNAUTHORIZED).json({ + errors: { + permissions: "sorry, you don't have the permission to perform this action" + } + }); +}; diff --git a/src/middlewares/checkUpdateNotification.js b/src/middlewares/checkUpdateNotification.js new file mode 100644 index 00000000..f892937e --- /dev/null +++ b/src/middlewares/checkUpdateNotification.js @@ -0,0 +1,11 @@ +import status from '../config/status'; + +export default async (req, res, next) => (req.user.role === 'normal' + && req.body + && req.body.preference + && res.status(status.UNAUTHORIZED).json({ + errors: { + permission: 'sorry, you can not update this notification' + } + })) + || next(); diff --git a/src/middlewares/checkUpdateUser.js b/src/middlewares/checkUpdateUser.js new file mode 100644 index 00000000..bd259304 --- /dev/null +++ b/src/middlewares/checkUpdateUser.js @@ -0,0 +1,32 @@ +import status from '../config/status'; +import * as helper from '../helpers'; +import { User } from '../queries'; + +export default async (req, res, next) => { + let [isEmailUsed, isSameOldEmail] = [false, false]; + req.changeEmail = { newEmail: '', message: '' }; + + if (req.body.password) { + req.body.password = helper.password.hash(req.body.password); + } + if (req.body.permissions) { + req.body.permissions = JSON.stringify(req.body.permissions); + } + if (req.body.email && req.user.role !== 'admin') { + const findUser = await User.findOne({ email: req.body.email }); + if (!findUser.errors && Object.keys(findUser).length > 0) { + isEmailUsed = req.user.id !== findUser.id; + isSameOldEmail = req.body.email === findUser.email; + } + if (!isSameOldEmail) { + req.changeEmail.newEmail = req.body.email; + req.changeEmail.message = 'Check your email to confirm your new email'; + } + delete req.body.email; + } + + return ( + (isEmailUsed && res.status(status.EXIST).json({ errors: { email: 'email already used' } })) + || next() + ); +}; diff --git a/src/middlewares/checkUpdateUserPermission.js b/src/middlewares/checkUpdateUserPermission.js new file mode 100644 index 00000000..947621d4 --- /dev/null +++ b/src/middlewares/checkUpdateUserPermission.js @@ -0,0 +1,25 @@ +import status from '../config/status'; + +export default (req, res, next) => { + req.userId = req.user.id; + const { role } = req.user; + let isAllowed = true; + + if (req.params && req.params.id) { + if (role === 'admin') { + req.userId = req.params.id; + } else { + isAllowed = false; + } + } + if (role === 'normal') { + if (req.body.role || req.body.permissions) { + isAllowed = false; + } + } + return isAllowed + ? next() + : res.status(status.UNAUTHORIZED).json({ + errors: { authentication: "sorry, you don't have the permission to update this account" } + }); +}; diff --git a/src/middlewares/isActiveUser.js b/src/middlewares/isActiveUser.js new file mode 100644 index 00000000..168b2659 --- /dev/null +++ b/src/middlewares/isActiveUser.js @@ -0,0 +1,31 @@ +import status from '../config/status'; +import * as helper from '../helpers'; +/** + * @param {object} req Request to the route + * @param {object} res Response from Sserver + * @param {object} next middleware called to pass after success + * @returns {object} returned response + */ +export default async (req, res, next) => { + const input = {}; + if (req.params && req.params.id) { + input.id = req.params.id; + } else if (req.params && req.params.username) { + input.username = req.params.username; + } else if (req.body && req.body.email) { + input.email = req.body.email; + } + const isUser = await helper.isUser(input); + if (!isUser) { + return res.status(status.UNAUTHORIZED).json({ + errors: { account: `user "${input.email || input.id || input.username}" not exist` } + }); + } + const isActive = await helper.isActiveUser(input); + if (!isActive && !(req.user && req.user.role === 'admin')) { + return res.status(status.UNAUTHORIZED).json({ + errors: { account: 'this account is not activated' } + }); + } + next(); +}; diff --git a/src/middlewares/logout.js b/src/middlewares/logout.js new file mode 100644 index 00000000..8482e7b2 --- /dev/null +++ b/src/middlewares/logout.js @@ -0,0 +1,20 @@ +import { Token } from '../queries'; +import status from '../config/status'; +import { clearInvalidToken } from '../helpers'; + +export default async (req, res) => { + const token = req.headers['access-token'] || req.params.token || null; + const { user } = req; + + const savedToken = await Token.save(token, user.id); + + if (savedToken.errors) { + return res + .status(status.SERVER_ERROR) + .json({ errors: 'Oops, something went wrong, please try again!' }); + } + + await clearInvalidToken(user.id); + + return res.status(status.OK).json({ message: 'you are successfully logged out' }); +}; diff --git a/src/middlewares/multerUploads.js b/src/middlewares/multerUploads.js new file mode 100644 index 00000000..e83684c6 --- /dev/null +++ b/src/middlewares/multerUploads.js @@ -0,0 +1,22 @@ +import multer from 'multer'; +import cloudinaryStorage from 'multer-storage-cloudinary'; +import cloudinary from 'cloudinary'; +import dotenv from 'dotenv'; +import cloudinaryConfig from '../config/cloudinaryConfig'; + +dotenv.config(); +const { NODE_ENV } = process.env; +const MAX_IMAGE_SIZE = 1 * 1024 * 1024; // Maximum allowed image size: 1MB + +const cdnConnect = cloudinaryConfig(); + +const storage = cloudinaryStorage({ + cloudinary, + folder: NODE_ENV !== 'test' ? cdnConnect.cloud_name : 'tests', + allowedFormat: ['jpg', 'png', 'jpeg'] +}); +const multerUploads = multer({ + storage, + limits: { fileSize: MAX_IMAGE_SIZE } +}); +export default multerUploads; diff --git a/src/middlewares/passport.js b/src/middlewares/passport.js new file mode 100644 index 00000000..76bde862 --- /dev/null +++ b/src/middlewares/passport.js @@ -0,0 +1,17 @@ +import passport from 'passport'; +import { Strategy as FacebookStrategy } from 'passport-facebook'; +import { Strategy as TwitterStrategy } from 'passport-twitter'; +import { OAuth2Strategy as GoogleStrategy } from 'passport-google-oauth'; +import passportConfig from '../config/passportConfig'; + +passport.use(new FacebookStrategy(passportConfig.facebook, passportConfig.facebook.callbackFunc)); + +passport.use(new TwitterStrategy(passportConfig.twitter, passportConfig.twitter.callbackFunc)); + +passport.use(new GoogleStrategy(passportConfig.google2, passportConfig.google2.callbackFunc)); + +passport.serializeUser(passportConfig.serializeUser); + +passport.deserializeUser(passportConfig.deserializeUser); + +export default passport; diff --git a/src/middlewares/shareArticle.js b/src/middlewares/shareArticle.js new file mode 100644 index 00000000..30b16c2c --- /dev/null +++ b/src/middlewares/shareArticle.js @@ -0,0 +1,29 @@ +import open from 'open'; +import dotenv from 'dotenv'; + +dotenv.config(); + +const { APP_URL_FRONTEND } = process.env.APP_URL_FRONTEND; +const facebookShareURL = `https://web.facebook.com/sharer/sharer.php?u=${APP_URL_FRONTEND}/articles/`; +const twitterShareURL = `https://twitter.com/intent/tweet?text=${APP_URL_FRONTEND}/articles/`; +const linkedinShareURL = `https://www.linkedin.com/sharing/share-offsite/?url=${APP_URL_FRONTEND}/articles/`; + +export default async (req, res, next) => { + const { article } = req; + if (req.url.search(/\/facebook/g) > 0) { + await open(`${facebookShareURL}${article.slug}`, { wait: false }); + } else if (req.url.search(/\/twitter/g) > 0) { + await open(`${twitterShareURL}${article.slug}`, { wait: false }); + } else if (req.url.search(/\/linkedin/g) > 0) { + await open(`${linkedinShareURL}${article.slug}`, { wait: false }); + } else if (req.url.search(/\/gmail/g) > 0) { + await open( + `mailto:?subject=${article.title}&body=${APP_URL_FRONTEND}/articles/${article.slug}`, + { + wait: false + } + ); + } + + next(); +}; diff --git a/src/middlewares/validateComment.js b/src/middlewares/validateComment.js new file mode 100644 index 00000000..27820962 --- /dev/null +++ b/src/middlewares/validateComment.js @@ -0,0 +1,16 @@ +import commentValidate from '../helpers/validation/comment'; +// eslint-disable-next-line valid-jsdoc +/** + * @param { object } req the request + * @param { object } res the respond + * @param { function } next + */ +export default function Validation(req, res, next) { + const { error } = commentValidate(req.body); + if (error) { + return res.status(400).send({ + message: error.details[0].message + }); + } + next(); +} diff --git a/src/middlewares/validateLogin.js b/src/middlewares/validateLogin.js new file mode 100644 index 00000000..ebec127a --- /dev/null +++ b/src/middlewares/validateLogin.js @@ -0,0 +1,26 @@ +import status from '../config/status'; + +export default async (req, res, next) => { + const [email, password] = [req.body.email || null, req.body.password || null]; + + const errors = { + email: + typeof email === 'string' && email.trim() + ? null + : 'email should not be empty and should be a string', + password: + typeof password === 'string' && password.trim() + ? null + : 'password should not be empty and should be a string' + }; + + Object.keys(errors).forEach(key => errors[key] || delete errors[key]); + + return ( + (Object.keys(errors).length + && res.status(status.BAD_REQUEST).json({ + errors + })) + || next() + ); +}; diff --git a/src/middlewares/validateNotificationConfig.js b/src/middlewares/validateNotificationConfig.js new file mode 100644 index 00000000..e502699b --- /dev/null +++ b/src/middlewares/validateNotificationConfig.js @@ -0,0 +1,16 @@ +import * as validate from '../helpers/validation'; +import status from '../config/status'; + +export default async (req, res, next) => { + const errors = {}; + const { error } = validate.notificationConfig(req.body); + + if (error) { + error.details.map((err) => { + errors[err.path[0]] = err.message; + return errors[err.path[0]]; + }); + } + + return !Object.keys(errors).length ? next() : res.status(status.BAD_REQUEST).json({ errors }); +}; diff --git a/src/middlewares/validateUser.js b/src/middlewares/validateUser.js new file mode 100644 index 00000000..f4971049 --- /dev/null +++ b/src/middlewares/validateUser.js @@ -0,0 +1,30 @@ +import * as validate from '../helpers/validation'; +import status from '../config/status'; + +export default async (req, res, next) => { + if (!Object.keys(req.body).length) { + return res.status(status.BAD_REQUEST).json({ errors: { body: 'should not be empty' } }); + } + const errors = {}; + const { error } = req.method === 'POST' ? validate.newUser(req.body) : validate.updateUser(req.body); + + if (error && typeof error === 'object' && Object.keys(error).length) { + error.details.forEach((err) => { + errors[err.path[0]] = err.message; + }); + } + + Object.keys(req.body).forEach((key) => { + const validatedField = (['firstName', 'lastName', 'username'].includes(key) + && validate.name(req.body[key], null, key)) + || (key === 'email' && validate.email(req.body.email, 'required')[0]) + || (key === 'password' && validate.password(req.body.password, 'required')[0]) + || null; + + if (validatedField && validatedField !== true) { + errors[key] = errors[key] || validatedField; + } + }); + + return Object.keys(errors).length ? res.status(status.BAD_REQUEST).json({ errors }) : next(); +}; diff --git a/src/middlewares/validation/articles.js b/src/middlewares/validation/articles.js new file mode 100644 index 00000000..b0d8200f --- /dev/null +++ b/src/middlewares/validation/articles.js @@ -0,0 +1,75 @@ +import Error from '../../helpers/errorHandler'; +import * as validate from '../../helpers'; + +/** + * A class to handle actions performed on articles + */ +class articles { + /** + * @param {object} req Request sent to the route + * @param {object} res Response from server + * @param {object} next If no error continue + * @returns {object} Object representing the response returned + */ + static create(req, res, next) { + const result = validate.validation.createArticle(req.body); + if (result.error) { + return Error.joiErrorHandler(res, result); + } + next(); + } + + /** + * @param {object} req Request sent to the route + * @param {object} res Response from server + * @param {object} next If no error continue + * @returns {object} Object representing the response returned + */ + static update(req, res, next) { + const response = validate.validation.updateArticle(req.body); + req.body = { + title: req.body.title && req.body.title.trim(), + body: req.body.body && req.body.body.trim(), + description: req.body.description && req.body.description.trim(), + readTime: req.body.body && validate.generator.readtime(req.body.body) + }; + Object.keys(req.body).forEach( + key => req.body[key] || (key !== 'readTime' && delete req.body[key]) + ); + return !response.error ? next() : Error.joiErrorHandler(res, response); + } + + /** + * @param {object} req Request sent to the route + * @param {object} res Response from server + * @param {object} next Log errors + * @returns {object} Object representing the response returned + */ + static slug(req, res, next) { + const response = validate.validation.articleSlug(req.params); + if (response.error) { + Error.joiErrorHandler(res, response); + } else { + next(); + } + } + + /** + * Validate pagination queries + * @param {object} req Request sent to the route + * @param {object} res Response from server + * @param {object} next Log errors + * @returns {object} Object representing the response returned + */ + static pagination(req, res, next) { + const duplicated = validate.parameters.duplication(req.query); + if (Object.keys(duplicated).length) { + return res.status(400).send({ errors: duplicated }); + } + + const { error } = validate.validation.queryParameters(req.query); + return !error ? next() : Error.joiErrorHandler(res, { error }); + } +} + +export default articles; diff --git a/src/middlewares/validation/highlights.js b/src/middlewares/validation/highlights.js new file mode 100644 index 00000000..58a82b01 --- /dev/null +++ b/src/middlewares/validation/highlights.js @@ -0,0 +1,20 @@ +import Error from '../../helpers/errorHandler'; +import * as validate from '../../helpers'; + +/** + * A class to handle highlights on articles + */ +class highlights { + /** + * @param {object} req Request sent to the route + * @param {object} res Response from server + * @param {object} next If no error continue + * @returns {object} Object representing the response returned + */ + static create(req, res, next) { + const result = validate.validation.createHighlight(req.body); + return result.error ? Error.joiErrorHandler(res, result) : next(); + } +} + +export default highlights; diff --git a/src/middlewares/validation/permissions.js b/src/middlewares/validation/permissions.js new file mode 100644 index 00000000..3b09d0e5 --- /dev/null +++ b/src/middlewares/validation/permissions.js @@ -0,0 +1,13 @@ +import Error from '../../helpers/errorHandler'; +import * as validate from '../../helpers/validation'; + +/** + * @param {object} req Request sent to the route + * @param {object} res Response from server + * @param {object} next If no error continue + * @returns {object} Object representing the response returned + */ +export default (req, res, next) => { + const result = validate.createPermissions(req.body); + return (result.error && Error.joiErrorHandler(res, result)) || next(); +}; diff --git a/src/middlewares/validation/validateRating.js b/src/middlewares/validation/validateRating.js new file mode 100644 index 00000000..7176abe0 --- /dev/null +++ b/src/middlewares/validation/validateRating.js @@ -0,0 +1,22 @@ +import status from '../../config/status'; + +/** + * Validate rating + */ +class rating { + /** + * @param {object} req Request sent to the route + * @param {object} res Response from server + * @param {object} next If no error continue + * @returns {object} Object representing the response returned + */ + static create(req, res, next) { + const errors = {}; + if (req.body && typeof req.body.rating !== 'number') errors.rating = 'rating must be a number'; + if (req.body.rating < 1 || req.body.rating > 5) errors.rating = 'rating must be between 1 and 5'; + if (errors.rating) return res.status(status.BAD_REQUEST).json({ errors }); + next(); + } +} + +export default rating; diff --git a/src/middlewares/validation/validateTags.js b/src/middlewares/validation/validateTags.js new file mode 100644 index 00000000..f18783e6 --- /dev/null +++ b/src/middlewares/validation/validateTags.js @@ -0,0 +1,21 @@ +import Error from '../../helpers/errorHandler'; +import * as validate from '../../helpers/validation'; + +/** + * Author: Gilles Kagarama + * @returns {object} Object representing the response returned + */ +class tags { + /** + * @param {object} req Request sent to the route + * @param {object} res Response from server + * @param {object} next If no error continue + * @returns {object} Object representing the response returned + */ + static create(req, res, next) { + const result = validate.createTag(req.body); + return result.error ? Error.joiErrorHandler(res, result) : next(); + } +} + +export default tags; diff --git a/src/middlewares/verifyAdmin.js b/src/middlewares/verifyAdmin.js new file mode 100644 index 00000000..84a9fa68 --- /dev/null +++ b/src/middlewares/verifyAdmin.js @@ -0,0 +1,23 @@ +import status from '../config/status'; +import isUser from '../helpers/isUser'; + +/** + * + * + * @export + * @param {object} req + * @param {object} res + * @param {void} next + * @returns {void} + */ +export default async (req, res, next) => { + const id = req.user ? req.user.id : 0; + const requestUser = await isUser({ id, role: 'admin' }); + + if (!requestUser) { + return res.status(status.ACCESS_DENIED).json({ + message: 'Permission denied, you are not allowed to perform this action' + }); + } + return next(); +}; diff --git a/src/middlewares/verifyToken.js b/src/middlewares/verifyToken.js new file mode 100644 index 00000000..98906ce7 --- /dev/null +++ b/src/middlewares/verifyToken.js @@ -0,0 +1,29 @@ +import status from '../config/status'; +import * as helper from '../helpers'; +import { Token } from '../queries'; + +export default async (req, res, next) => { + const token = req.headers['access-token'] || req.params.token || null; + + if (!token) { + return res.status(status.UNAUTHORIZED).json({ errors: { authentication: 'Please, sign-in!' } }); + } + + const decodedToken = helper.token.decode(token); + + if (decodedToken.errors || !decodedToken) { + return res + .status(status.UNAUTHORIZED) + .json({ errors: { token: 'Failed to authenticate token' } }); + } + + const isLoggedout = decodedToken.id ? await Token.findOne(decodedToken.id, token) : {}; + + if (!isLoggedout.errors && Object.keys(isLoggedout).length) { + return res.status(status.UNAUTHORIZED).json({ errors: { token: 'This token is invalid' } }); + } + + req.user = decodedToken; + + return next(); +}; diff --git a/src/migrations/20190427180244-create-user.js b/src/migrations/20190427180244-create-user.js new file mode 100644 index 00000000..61d103fb --- /dev/null +++ b/src/migrations/20190427180244-create-user.js @@ -0,0 +1,66 @@ +export default { + up: (queryInterface, Sequelize) => queryInterface.createTable('Users', { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: Sequelize.INTEGER + }, + firstName: { + type: Sequelize.STRING, + allowNull: false + }, + lastName: { + type: Sequelize.STRING, + allowNull: false + }, + username: { + type: Sequelize.STRING, + allowNull: true, + unique: true + }, + email: { + type: Sequelize.STRING, + allowNull: true, + unique: true + }, + password: { + type: Sequelize.STRING, + allowNull: true + }, + bio: { + type: Sequelize.TEXT, + allowNull: true + }, + image: { + type: Sequelize.STRING, + allowNull: true + }, + role: { + type: Sequelize.ENUM('normal', 'admin'), + allowNull: false, + defaultValue: 'normal' + }, + permissions: { + type: Sequelize.ARRAY(Sequelize.STRING), + allowNull: true + }, + accountProvider: { + type: Sequelize.STRING, + allowNull: true + }, + accountProviderUserId: { + type: Sequelize.STRING, + allowNull: true + }, + createdAt: { + allowNull: false, + type: Sequelize.DATE + }, + updatedAt: { + allowNull: false, + type: Sequelize.DATE + } + }), + down: queryInterface => queryInterface.dropTable('Users') +}; diff --git a/src/migrations/20190427195129-create-article.js b/src/migrations/20190427195129-create-article.js new file mode 100644 index 00000000..c6f8642c --- /dev/null +++ b/src/migrations/20190427195129-create-article.js @@ -0,0 +1,73 @@ +export default { + up: (queryInterface, Sequelize) => queryInterface.createTable('Articles', { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: Sequelize.INTEGER + }, + userId: { + type: Sequelize.INTEGER, + allowNull: false, + references: { + model: 'Users', + key: 'id' + }, + onUpdate: 'CASCADE', + onDelete: 'CASCADE' + }, + slug: { + type: Sequelize.STRING, + allowNull: false, + unique: true + }, + title: { + type: Sequelize.STRING, + allowNull: false + }, + description: { + type: Sequelize.STRING, + allowNull: true + }, + body: { + type: Sequelize.TEXT, + allowNull: false + }, + tagList: { + type: Sequelize.ARRAY(Sequelize.STRING), + allowNull: true + }, + status: { + type: Sequelize.ENUM('draft', 'published', 'deleted'), + allowNull: false, + defaultValue: 'draft' + }, + coverUrl: { + type: Sequelize.TEXT, + allowNull: true + }, + readTime: { + type: Sequelize.STRING, + allowNull: true + }, + favorited: { + type: Sequelize.BOOLEAN, + allowNull: true, + defaultValue: false + }, + favoritesCount: { + type: Sequelize.INTEGER, + allowNull: true, + defaultValue: 0 + }, + createdAt: { + allowNull: false, + type: Sequelize.DATE + }, + updatedAt: { + allowNull: false, + type: Sequelize.DATE + } + }), + down: queryInterface => queryInterface.dropTable('Articles') +}; diff --git a/src/migrations/20190428161016-create-comment.js b/src/migrations/20190428161016-create-comment.js new file mode 100644 index 00000000..7b9bcc78 --- /dev/null +++ b/src/migrations/20190428161016-create-comment.js @@ -0,0 +1,47 @@ +export default { + up: (queryInterface, Sequelize) => queryInterface.createTable('Comments', { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: Sequelize.INTEGER + }, + articleSlug: { + type: Sequelize.STRING, + allowNull: false, + references: { + model: 'Articles', + key: 'slug' + }, + onUpdate: 'CASCADE', + onDelete: 'CASCADE' + }, + userId: { + type: Sequelize.INTEGER, + allowNull: true, + references: { + model: 'Users', + key: 'id' + }, + onUpdate: 'CASCADE', + onDelete: 'SET NULL' + }, + body: { + type: Sequelize.TEXT, + allowNull: false + }, + likes: { + type: Sequelize.INTEGER, + allowNull: true + }, + createdAt: { + allowNull: false, + type: Sequelize.DATE + }, + updatedAt: { + allowNull: false, + type: Sequelize.DATE + } + }), + down: queryInterface => queryInterface.dropTable('Comments') +}; diff --git a/src/migrations/20190509005606-create-token.js b/src/migrations/20190509005606-create-token.js new file mode 100644 index 00000000..9709ad32 --- /dev/null +++ b/src/migrations/20190509005606-create-token.js @@ -0,0 +1,33 @@ +export default { + up: (queryInterface, Sequelize) => queryInterface.createTable('Tokens', { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: Sequelize.INTEGER + }, + userId: { + type: Sequelize.INTEGER, + allowNull: false, + references: { + model: 'Users', + key: 'id' + }, + onUpdate: 'CASCADE', + onDelete: 'CASCADE' + }, + token: { + type: Sequelize.STRING, + allowNull: false + }, + createdAt: { + allowNull: false, + type: Sequelize.DATE + }, + updatedAt: { + allowNull: false, + type: Sequelize.DATE + } + }), + down: queryInterface => queryInterface.dropTable('Tokens') +}; diff --git a/src/migrations/20190512134435-create-comment-like.js b/src/migrations/20190512134435-create-comment-like.js new file mode 100644 index 00000000..f3fe7ecc --- /dev/null +++ b/src/migrations/20190512134435-create-comment-like.js @@ -0,0 +1,35 @@ +export default { + up: (queryInterface, Sequelize) => queryInterface.createTable('CommentLikes', { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: Sequelize.INTEGER + }, + userId: { + type: Sequelize.INTEGER + }, + commentId: { + type: Sequelize.INTEGER + }, + articleSlug: { + type: Sequelize.STRING, + allowNull: false, + references: { + model: 'Articles', + key: 'slug' + }, + onUpdate: 'CASCADE', + onDelete: 'CASCADE' + }, + createdAt: { + allowNull: false, + type: Sequelize.DATE + }, + updatedAt: { + allowNull: false, + type: Sequelize.DATE + } + }), + down: queryInterface => queryInterface.dropTable('CommentLikes') +}; diff --git a/src/migrations/20190514082632-create-article-like.js b/src/migrations/20190514082632-create-article-like.js new file mode 100644 index 00000000..c2b8751c --- /dev/null +++ b/src/migrations/20190514082632-create-article-like.js @@ -0,0 +1,43 @@ +export default { + up: (queryInterface, Sequelize) => queryInterface.createTable('ArticleLikes', { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: Sequelize.INTEGER + }, + userId: { + type: Sequelize.INTEGER, + allowNull: false, + references: { + model: 'Users', + key: 'id' + }, + onUpdate: 'CASCADE', + onDelete: 'SET NULL' + }, + articleSlug: { + type: Sequelize.STRING, + allowNull: false, + references: { + model: 'Articles', + key: 'slug' + }, + onUpdate: 'CASCADE', + onDelete: 'SET NULL' + }, + status: { + type: Sequelize.ENUM('dislike', 'like'), + allowNull: false + }, + createdAt: { + allowNull: false, + type: Sequelize.DATE + }, + updatedAt: { + allowNull: false, + type: Sequelize.DATE + } + }), + down: queryInterface => queryInterface.dropTable('ArticleLikes') +}; diff --git a/src/migrations/20190517093451-create-article-bookmark.js b/src/migrations/20190517093451-create-article-bookmark.js new file mode 100644 index 00000000..12560e2d --- /dev/null +++ b/src/migrations/20190517093451-create-article-bookmark.js @@ -0,0 +1,27 @@ +export default { + up: (queryInterface, Sequelize) => queryInterface.createTable('ArticleBookmarks', { + userId: { + type: Sequelize.INTEGER, + allowNull: false, + primaryKey: true, + references: { + model: 'Users', + key: 'id' + } + }, + articleSlug: { + type: Sequelize.STRING, + allowNull: false, + primaryKey: true + }, + createdAt: { + allowNull: false, + type: Sequelize.DATE + }, + updatedAt: { + allowNull: false, + type: Sequelize.DATE + } + }), + down: queryInterface => queryInterface.dropTable('ArticleBookmarks') +}; diff --git a/src/migrations/20190519034940-create-ratings.js b/src/migrations/20190519034940-create-ratings.js new file mode 100644 index 00000000..d4bcf6da --- /dev/null +++ b/src/migrations/20190519034940-create-ratings.js @@ -0,0 +1,43 @@ +export default { + up: (queryInterface, Sequelize) => queryInterface.createTable('Ratings', { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: Sequelize.INTEGER + }, + userId: { + type: Sequelize.INTEGER, + allowNull: false, + references: { + model: 'Users', + key: 'id' + }, + onUpdate: 'CASCADE', + onDelete: 'CASCADE' + }, + articleId: { + type: Sequelize.INTEGER, + allowNull: false, + references: { + model: 'Articles', + key: 'id' + }, + onUpdate: 'CASCADE', + onDelete: 'CASCADE' + }, + rating: { + type: Sequelize.INTEGER, + allowNull: false + }, + createdAt: { + allowNull: false, + type: Sequelize.DATE + }, + updatedAt: { + allowNull: false, + type: Sequelize.DATE + } + }), + down: queryInterface => queryInterface.dropTable('Ratings') +}; diff --git a/src/migrations/20190519224114-create-follows.js b/src/migrations/20190519224114-create-follows.js new file mode 100644 index 00000000..688a94d0 --- /dev/null +++ b/src/migrations/20190519224114-create-follows.js @@ -0,0 +1,31 @@ +export default { + up: (queryInterface, Sequelize) => queryInterface.createTable('Follows', { + userId: { + type: Sequelize.INTEGER, + allowNull: false, + primaryKey: true, + references: { + model: 'Users', + key: 'id' + } + }, + followed: { + type: Sequelize.INTEGER, + allowNull: false, + primaryKey: true, + references: { + model: 'Users', + key: 'id' + } + }, + createdAt: { + allowNull: false, + type: Sequelize.DATE + }, + updatedAt: { + allowNull: false, + type: Sequelize.DATE + } + }), + down: queryInterface => queryInterface.dropTable('Follows') +}; diff --git a/src/migrations/20190520223347-create-chat.js b/src/migrations/20190520223347-create-chat.js new file mode 100644 index 00000000..4f3fc769 --- /dev/null +++ b/src/migrations/20190520223347-create-chat.js @@ -0,0 +1,38 @@ +export default { + up: (queryInterface, Sequelize) => queryInterface.createTable('Chats', { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: Sequelize.INTEGER + }, + userId: { + type: Sequelize.INTEGER, + allowNull: false, + references: { + model: 'Users', + key: 'id' + }, + onUpdate: 'CASCADE', + onDelete: 'CASCADE' + }, + message: { + type: Sequelize.TEXT, + allowNull: false + }, + chatGroupId: { + type: Sequelize.INTEGER, + allowNull: true, + defaultValue: 1 + }, + createdAt: { + allowNull: false, + type: Sequelize.DATE + }, + updatedAt: { + allowNull: false, + type: Sequelize.DATE + } + }), + down: queryInterface => queryInterface.dropTable('Chats') +}; diff --git a/src/migrations/20190520223439-create-chat-group.js b/src/migrations/20190520223439-create-chat-group.js new file mode 100644 index 00000000..1c1c7711 --- /dev/null +++ b/src/migrations/20190520223439-create-chat-group.js @@ -0,0 +1,23 @@ +export default { + up: (queryInterface, Sequelize) => queryInterface.createTable('ChatGroups', { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: Sequelize.INTEGER + }, + name: { + type: Sequelize.STRING, + allowNull: false + }, + createdAt: { + allowNull: false, + type: Sequelize.DATE + }, + updatedAt: { + allowNull: false, + type: Sequelize.DATE + } + }), + down: queryInterface => queryInterface.dropTable('ChatGroups') +}; diff --git a/src/migrations/20190520224040-create-chat-group-member.js b/src/migrations/20190520224040-create-chat-group-member.js new file mode 100644 index 00000000..ed8a632e --- /dev/null +++ b/src/migrations/20190520224040-create-chat-group-member.js @@ -0,0 +1,35 @@ +export default { + up: (queryInterface, Sequelize) => queryInterface.createTable('ChatGroupMembers', { + chatGroupId: { + primaryKey: true, + type: Sequelize.INTEGER, + allowNull: false, + references: { + model: 'ChatGroups', + key: 'id' + }, + onUpdate: 'CASCADE', + onDelete: 'CASCADE' + }, + userId: { + primaryKey: true, + type: Sequelize.INTEGER, + allowNull: false, + references: { + model: 'Users', + key: 'id' + }, + onUpdate: 'CASCADE', + onDelete: 'CASCADE' + }, + createdAt: { + allowNull: false, + type: Sequelize.DATE + }, + updatedAt: { + allowNull: false, + type: Sequelize.DATE + } + }), + down: queryInterface => queryInterface.dropTable('ChatGroupMembers') +}; diff --git a/src/migrations/20190521145336-create-permission.js b/src/migrations/20190521145336-create-permission.js new file mode 100644 index 00000000..8506291b --- /dev/null +++ b/src/migrations/20190521145336-create-permission.js @@ -0,0 +1,28 @@ +export default { + up: (queryInterface, Sequelize) => queryInterface.createTable('Permissions', { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: Sequelize.INTEGER + }, + userType: { + type: Sequelize.STRING, + unique: true, + allowNull: false + }, + permissions: { + type: Sequelize.STRING, + allowNull: false + }, + createdAt: { + allowNull: false, + type: Sequelize.DATE + }, + updatedAt: { + allowNull: false, + type: Sequelize.DATE + } + }), + down: queryInterface => queryInterface.dropTable('Permissions') +}; diff --git a/src/migrations/20190523204918-create-report.js b/src/migrations/20190523204918-create-report.js new file mode 100644 index 00000000..2d7d49bb --- /dev/null +++ b/src/migrations/20190523204918-create-report.js @@ -0,0 +1,52 @@ +export default { + up: (queryInterface, Sequelize) => queryInterface.createTable('Reports', { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: Sequelize.INTEGER + }, + articleSlug: { + type: Sequelize.STRING, + allowNull: false, + references: { + model: 'Articles', + key: 'slug' + }, + onUpdate: 'CASCADE', + onDelete: 'CASCADE' + }, + userId: { + type: Sequelize.INTEGER, + allowNull: true, + references: { + model: 'Users', + key: 'id' + }, + onUpdate: 'CASCADE', + onDelete: 'SET NULL' + }, + title: { + type: Sequelize.STRING, + allowNull: false + }, + + body: { + type: Sequelize.TEXT, + allowNull: false + }, + type: { + allowNull: true, + type: Sequelize.ARRAY(Sequelize.STRING) + }, + createdAt: { + allowNull: false, + type: Sequelize.DATE + }, + updatedAt: { + allowNull: false, + type: Sequelize.DATE + } + }), + down: queryInterface => queryInterface.dropTable('Reports') +}; diff --git a/src/migrations/20190527082455-create-notification.js b/src/migrations/20190527082455-create-notification.js new file mode 100644 index 00000000..24c5d3e4 --- /dev/null +++ b/src/migrations/20190527082455-create-notification.js @@ -0,0 +1,38 @@ +export default { + up: (queryInterface, Sequelize) => queryInterface.createTable('Notifications', { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: Sequelize.INTEGER + }, + userId: { + type: Sequelize.INTEGER, + allowNull: false, + references: { + model: 'Users', + key: 'id' + }, + onUpdate: 'CASCADE', + onDelete: 'CASCADE' + }, + message: { + type: Sequelize.TEXT, + allowNull: false + }, + status: { + type: Sequelize.ENUM('seen', 'unseen'), + allowNull: false, + defaultValue: 'unseen' + }, + createdAt: { + allowNull: false, + type: Sequelize.DATE + }, + updatedAt: { + allowNull: false, + type: Sequelize.DATE + } + }), + down: queryInterface => queryInterface.dropTable('Notifications') +}; diff --git a/src/migrations/20190527141438-create-notification-config.js b/src/migrations/20190527141438-create-notification-config.js new file mode 100644 index 00000000..4f883121 --- /dev/null +++ b/src/migrations/20190527141438-create-notification-config.js @@ -0,0 +1,34 @@ +export default { + up: (queryInterface, Sequelize) => queryInterface.createTable('NotificationConfigs', { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: Sequelize.INTEGER + }, + userId: { + type: Sequelize.INTEGER, + allowNull: false, + unique: true, + references: { + model: 'Users', + key: 'id' + }, + onUpdate: 'CASCADE', + onDelete: 'CASCADE' + }, + config: { + type: Sequelize.STRING, + allowNull: false + }, + createdAt: { + allowNull: false, + type: Sequelize.DATE + }, + updatedAt: { + allowNull: false, + type: Sequelize.DATE + } + }), + down: queryInterface => queryInterface.dropTable('NotificationConfigs') +}; diff --git a/src/migrations/20190528063248-create-comment-edit.js b/src/migrations/20190528063248-create-comment-edit.js new file mode 100644 index 00000000..498cf830 --- /dev/null +++ b/src/migrations/20190528063248-create-comment-edit.js @@ -0,0 +1,47 @@ +export default { + up: (queryInterface, Sequelize) => queryInterface.createTable('CommentEdits', { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: Sequelize.INTEGER + }, + articleSlug: { + type: Sequelize.STRING, + allowNull: false, + references: { + model: 'Articles', + key: 'slug' + }, + onUpdate: 'CASCADE', + onDelete: 'CASCADE' + }, + userId: { + type: Sequelize.INTEGER, + allowNull: true, + references: { + model: 'Users', + key: 'id' + }, + onUpdate: 'CASCADE', + onDelete: 'SET NULL' + }, + body: { + type: Sequelize.TEXT, + allowNull: false + }, + commentId: { + allowNull: null, + type: Sequelize.INTEGER + }, + createdAt: { + allowNull: false, + type: Sequelize.DATE + }, + updatedAt: { + allowNull: false, + type: Sequelize.DATE + } + }), + down: queryInterface => queryInterface.dropTable('CommentEdits') +}; diff --git a/src/migrations/20190528121111-modify-permission-column.js b/src/migrations/20190528121111-modify-permission-column.js new file mode 100644 index 00000000..5044fbec --- /dev/null +++ b/src/migrations/20190528121111-modify-permission-column.js @@ -0,0 +1,15 @@ +export default { + up: (queryInterface, Sequelize) => Promise.all([ + queryInterface.changeColumn('Users', 'permissions', { + type: Sequelize.STRING, + allowNull: true + }) + ]), + + down: (queryInterface, Sequelize) => Promise.all([ + queryInterface.changeColumn('Users', 'permissions', { + type: Sequelize.STRING, + allowNull: true + }) + ]) +}; diff --git a/src/migrations/20190529093417-create-highlight.js b/src/migrations/20190529093417-create-highlight.js new file mode 100644 index 00000000..5da771ba --- /dev/null +++ b/src/migrations/20190529093417-create-highlight.js @@ -0,0 +1,51 @@ +module.exports = { + up: (queryInterface, Sequelize) => queryInterface.createTable('Highlights', { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: Sequelize.INTEGER + }, + articleSlug: { + type: Sequelize.STRING, + allowNull: false + }, + userId: { + type: Sequelize.INTEGER, + allowNull: true, + references: { + model: 'Users', + key: 'id' + }, + onUpdate: 'CASCADE', + onDelete: 'SET NULL' + }, + highlightedText: { + type: Sequelize.STRING, + allowNull: false, + field: 'highlightedText' + }, + startIndex: { + type: Sequelize.INTEGER, + allowNull: false, + field: 'startIndex' + }, + stopIndex: { + type: Sequelize.INTEGER, + allowNull: false, + field: 'stopIndex' + }, + comment: { + type: Sequelize.TEXT + }, + createdAt: { + allowNull: false, + type: Sequelize.DATE + }, + updatedAt: { + allowNull: false, + type: Sequelize.DATE + } + }), + down: queryInterface => queryInterface.dropTable('Highlights') +}; diff --git a/src/migrations/20190529140817-create-favorite-article.js b/src/migrations/20190529140817-create-favorite-article.js new file mode 100644 index 00000000..db00c710 --- /dev/null +++ b/src/migrations/20190529140817-create-favorite-article.js @@ -0,0 +1,27 @@ +export default { + up: (queryInterface, Sequelize) => queryInterface.createTable('FavoriteArticles', { + userId: { + type: Sequelize.INTEGER, + allowNull: false, + primaryKey: true, + references: { + model: 'Users', + key: 'id' + } + }, + articleSlug: { + type: Sequelize.STRING, + allowNull: false, + primaryKey: true + }, + createdAt: { + allowNull: false, + type: Sequelize.DATE + }, + updatedAt: { + allowNull: false, + type: Sequelize.DATE + } + }), + down: queryInterface => queryInterface.dropTable('FavoriteArticles') +}; diff --git a/src/migrations/20190603085831-create-gallery.js b/src/migrations/20190603085831-create-gallery.js new file mode 100644 index 00000000..ac5e4b02 --- /dev/null +++ b/src/migrations/20190603085831-create-gallery.js @@ -0,0 +1,33 @@ +export default { + up: (queryInterface, Sequelize) => queryInterface.createTable('Galleries', { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: Sequelize.INTEGER + }, + userId: { + type: Sequelize.INTEGER, + allowNull: false, + references: { + model: 'Users', + key: 'id' + }, + onUpdate: 'CASCADE', + onDelete: 'CASCADE' + }, + image: { + type: Sequelize.STRING, + allowNull: false + }, + createdAt: { + allowNull: false, + type: Sequelize.DATE + }, + updatedAt: { + allowNull: false, + type: Sequelize.DATE + } + }), + down: queryInterface => queryInterface.dropTable('Galleries') +}; diff --git a/src/migrations/20190603143322-add-likes-column.js b/src/migrations/20190603143322-add-likes-column.js new file mode 100644 index 00000000..02d02726 --- /dev/null +++ b/src/migrations/20190603143322-add-likes-column.js @@ -0,0 +1,10 @@ +export default { + up: (queryInterface, Sequelize) => queryInterface.addColumn('Articles', 'likes', { + type: Sequelize.INTEGER, + allowNull: true + }), + down: (queryInterface, Sequelize) => queryInterface.removeColumn('Articles', 'likes', { + type: Sequelize.INTEGER, + allowNull: true + }) +}; diff --git a/src/migrations/20190603144142-add-dislikes-column.js b/src/migrations/20190603144142-add-dislikes-column.js new file mode 100644 index 00000000..8722915c --- /dev/null +++ b/src/migrations/20190603144142-add-dislikes-column.js @@ -0,0 +1,10 @@ +export default { + up: (queryInterface, Sequelize) => queryInterface.addColumn('Articles', 'dislikes', { + type: Sequelize.INTEGER, + allowNull: true + }), + down: (queryInterface, Sequelize) => queryInterface.removeColumn('Articles', 'dislikes', { + type: Sequelize.INTEGER, + allowNull: true + }) +}; diff --git a/src/migrations/20190606145424-create-reading-stat.js b/src/migrations/20190606145424-create-reading-stat.js new file mode 100644 index 00000000..492e5433 --- /dev/null +++ b/src/migrations/20190606145424-create-reading-stat.js @@ -0,0 +1,34 @@ + +module.exports = { + up: (queryInterface, Sequelize) => queryInterface.createTable('ReadingStats', { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: Sequelize.INTEGER + }, + userId: { + type: Sequelize.INTEGER, + allowNull: true, + references: { + model: 'Users', + key: 'id' + }, + onUpdate: 'CASCADE', + onDelete: 'SET NULL' + }, + articleSlug: { + type: Sequelize.STRING, + allowNull: false + }, + createdAt: { + allowNull: false, + type: Sequelize.DATE + }, + updatedAt: { + allowNull: false, + type: Sequelize.DATE + } + }), + down: (queryInterface, Sequelize) => queryInterface.dropTable('ReadingStats') +}; diff --git a/src/migrations/20190608160408-add-notification-preference-column.js b/src/migrations/20190608160408-add-notification-preference-column.js new file mode 100644 index 00000000..f4d3eea9 --- /dev/null +++ b/src/migrations/20190608160408-add-notification-preference-column.js @@ -0,0 +1,14 @@ +export default { + up: (queryInterface, Sequelize) => Promise.all([ + queryInterface.sequelize.query('DROP TYPE IF EXISTS "enum_Notifications_preference"'), + queryInterface.addColumn('Notifications', 'preference', { + type: Sequelize.ENUM('inApp', 'email'), + allowNull: false, + defaultValue: 'inApp' + }) + ]), + down: queryInterface => Promise.all([ + queryInterface.removeColumn('Notifications', 'preference'), + queryInterface.sequelize.query('DROP TYPE IF EXISTS "enum_Notifications_preference"') + ]) +}; diff --git a/src/migrations/20190609181916-add-notification-url-column.js b/src/migrations/20190609181916-add-notification-url-column.js new file mode 100644 index 00000000..33f6bd6d --- /dev/null +++ b/src/migrations/20190609181916-add-notification-url-column.js @@ -0,0 +1,9 @@ +export default { + up: (queryInterface, Sequelize) => Promise.all([ + queryInterface.addColumn('Notifications', 'url', { + type: Sequelize.STRING, + allowNull: true + }) + ]), + down: queryInterface => Promise.all([queryInterface.removeColumn('Notifications', 'url')]) +}; diff --git a/src/migrations/20190611085335-add-user-isActive-column.js b/src/migrations/20190611085335-add-user-isActive-column.js new file mode 100644 index 00000000..d5d37a05 --- /dev/null +++ b/src/migrations/20190611085335-add-user-isActive-column.js @@ -0,0 +1,8 @@ +export default { + up: (queryInterface, Sequelize) => queryInterface.addColumn('Users', 'isActive', { + type: Sequelize.BOOLEAN, + allowNull: false, + defaultValue: false + }), + down: queryInterface => queryInterface.removeColumn('Users', 'isActive') +}; diff --git a/src/migrations/20190611100644-modify-article-coverUrl-column.js b/src/migrations/20190611100644-modify-article-coverUrl-column.js new file mode 100644 index 00000000..47bc1b46 --- /dev/null +++ b/src/migrations/20190611100644-modify-article-coverUrl-column.js @@ -0,0 +1,10 @@ +export default { + up: (queryInterface, Sequelize) => queryInterface.changeColumn('Articles', 'coverUrl', { + type: Sequelize.TEXT, + allowNull: true + }), + down: (queryInterface, Sequelize) => queryInterface.changeColumn('Articles', 'coverUrl', { + type: Sequelize.TEXT, + allowNull: false + }) +}; diff --git a/src/migrations/20190611154523-add-article-rating-column.js b/src/migrations/20190611154523-add-article-rating-column.js new file mode 100644 index 00000000..6b4e7857 --- /dev/null +++ b/src/migrations/20190611154523-add-article-rating-column.js @@ -0,0 +1,8 @@ +export default { + up: (queryInterface, Sequelize) => queryInterface.addColumn('Articles', 'rating', { + type: Sequelize.INTEGER, + allowNull: true, + defaultValue: 0 + }), + down: queryInterface => queryInterface.removeColumn('Articles', 'rating') +}; diff --git a/src/migrations/20190612124112-change-readtime-column-type.js b/src/migrations/20190612124112-change-readtime-column-type.js new file mode 100644 index 00000000..2c732144 --- /dev/null +++ b/src/migrations/20190612124112-change-readtime-column-type.js @@ -0,0 +1,11 @@ +export default { + up: queryInterface => queryInterface.changeColumn('Articles', 'readTime', { + type: 'integer USING CAST("readTime" AS integer)', + allowNull: false, + defaultValue: 0 + }), + down: (queryInterface, Sequelize) => queryInterface.changeColumn('Articles', 'readTime', { + type: Sequelize.STRING, + allowNull: true + }) +}; diff --git a/src/migrations/20190715122848-modify-token-column-length.js b/src/migrations/20190715122848-modify-token-column-length.js new file mode 100644 index 00000000..0627bdb6 --- /dev/null +++ b/src/migrations/20190715122848-modify-token-column-length.js @@ -0,0 +1,10 @@ +export default { + up: (queryInterface, Sequelize) => queryInterface.changeColumn('Tokens', 'token', { + type: Sequelize.TEXT, + allowNull: false + }), + down: (queryInterface, Sequelize) => queryInterface.changeColumn('Tokens', 'token', { + type: Sequelize.TEXT, + allowNull: false + }) +}; diff --git a/src/migrations/20190717132403-change-rating-column-in-article.js b/src/migrations/20190717132403-change-rating-column-in-article.js new file mode 100644 index 00000000..10426dfc --- /dev/null +++ b/src/migrations/20190717132403-change-rating-column-in-article.js @@ -0,0 +1,11 @@ +export default { + up: queryInterface => queryInterface.changeColumn('Articles', 'rating', { + type: 'float USING CAST("rating" AS float)', + allowNull: false, + defaultValue: 0 + }), + down: (queryInterface, Sequelize) => queryInterface.changeColumn('Articles', 'rating', { + type: Sequelize.INTEGER, + allowNull: true + }) +}; diff --git a/src/migrations/20190721221857-modify-highlight-highlightedText-column.js b/src/migrations/20190721221857-modify-highlight-highlightedText-column.js new file mode 100644 index 00000000..d7528239 --- /dev/null +++ b/src/migrations/20190721221857-modify-highlight-highlightedText-column.js @@ -0,0 +1,10 @@ +export default { + up: (queryInterface, Sequelize) => queryInterface.changeColumn('Highlights', 'highlightedText', { + type: Sequelize.TEXT, + allowNull: false + }), + down: (queryInterface, Sequelize) => queryInterface.changeColumn('Highlights', 'highlightedText', { + type: Sequelize.TEXT, + allowNull: false + }) +}; diff --git a/src/migrations/20190723093356-add-highlight-anchor-key-column.js b/src/migrations/20190723093356-add-highlight-anchor-key-column.js new file mode 100644 index 00000000..3ba30d88 --- /dev/null +++ b/src/migrations/20190723093356-add-highlight-anchor-key-column.js @@ -0,0 +1,7 @@ +export default { + up: (queryInterface, Sequelize) => queryInterface.addColumn('Highlights', 'anchorKey', { + type: Sequelize.STRING, + allowNull: true + }), + down: queryInterface => queryInterface.removeColumn('Highlights', 'anchorKey') +}; diff --git a/src/migrations/20190724135429-modify-articleslug-column-articleBookmark.js b/src/migrations/20190724135429-modify-articleslug-column-articleBookmark.js new file mode 100644 index 00000000..102a0348 --- /dev/null +++ b/src/migrations/20190724135429-modify-articleslug-column-articleBookmark.js @@ -0,0 +1,16 @@ +export default { + up: (queryInterface, Sequelize) => queryInterface.changeColumn('ArticleBookmarks', 'articleSlug', { + type: Sequelize.STRING, + allowNull: false, + primaryKey: true, + references: { + model: 'Articles', + key: 'slug' + } + }), + down: (queryInterface, Sequelize) => queryInterface.changeColumn('ArticleBookmarks', 'articleSlug', { + type: Sequelize.STRING, + allowNull: false, + primaryKey: true + }) +}; diff --git a/src/models/article.js b/src/models/article.js new file mode 100644 index 00000000..31b2055e --- /dev/null +++ b/src/models/article.js @@ -0,0 +1,108 @@ +import eventEmitter from '../helpers/eventEmitter'; + +export default (sequelize, DataTypes) => { + const Article = sequelize.define( + 'Article', + { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: DataTypes.INTEGER + }, + userId: { + type: DataTypes.INTEGER, + allowNull: false, + references: { + model: 'Users', + key: 'id' + }, + onUpdate: 'CASCADE', + onDelete: 'CASCADE' + }, + slug: { + type: DataTypes.STRING, + allowNull: false, + unique: true + }, + title: { + type: DataTypes.STRING, + allowNull: false + }, + description: { + type: DataTypes.STRING, + allowNull: true + }, + body: { + type: DataTypes.TEXT, + allowNull: false + }, + tagList: { + type: DataTypes.ARRAY(DataTypes.STRING), + allowNull: true + }, + status: { + type: DataTypes.ENUM('draft', 'published', 'deleted'), + allowNull: false, + defaultValue: 'draft' + }, + readTime: { + type: DataTypes.STRING, + allowNull: true + }, + coverUrl: { + type: DataTypes.STRING, + allowNull: true + }, + favorited: { + type: DataTypes.BOOLEAN, + allowNull: true, + defaultValue: false + }, + favoritesCount: { + type: DataTypes.INTEGER, + allowNull: true, + defaultValue: 0 + }, + likes: { + allowNull: true, + type: DataTypes.INTEGER + }, + dislikes: { + allowNull: true, + type: DataTypes.INTEGER + }, + rating: { + type: DataTypes.INTEGER, + allowNull: true, + defaultValue: 0 + }, + createdAt: { + allowNull: false, + type: DataTypes.DATE + }, + updatedAt: { + allowNull: false, + type: DataTypes.DATE + } + }, + { + hooks: { + afterUpdate: async (article) => { + const currentData = article.get(); + const previousData = article.previous(); + + if (previousData && previousData.status) { + if (previousData.status === 'draft' && currentData.status === 'published') { + eventEmitter.emit('publishArticle', currentData.userId, currentData.slug); + } + } + } + } + } + ); + Article.associate = (models) => { + Article.belongsTo(models.User, { foreignKey: 'userId', as: 'author' }); + }; + return Article; +}; diff --git a/src/models/articlebookmark.js b/src/models/articlebookmark.js new file mode 100644 index 00000000..7f71fb00 --- /dev/null +++ b/src/models/articlebookmark.js @@ -0,0 +1,39 @@ +export default (sequelize, DataTypes) => { + const ArticleBookmark = sequelize.define( + 'ArticleBookmark', + { + userId: { + type: DataTypes.INTEGER, + allowNull: false, + primaryKey: true, + references: { + model: 'Users', + key: 'id' + } + }, + articleSlug: { + type: DataTypes.STRING, + allowNull: false, + primaryKey: true + }, + createdAt: { + allowNull: false, + type: DataTypes.DATE + }, + updatedAt: { + allowNull: false, + type: DataTypes.DATE + } + }, + {} + ); + ArticleBookmark.associate = (models) => { + ArticleBookmark.belongsTo(models.User, { foreignKey: 'userId' }); + ArticleBookmark.belongsTo(models.Article, { + foreignKey: 'articleSlug', + targetKey: 'slug', + as: 'article' + }); + }; + return ArticleBookmark; +}; diff --git a/src/models/articlelike.js b/src/models/articlelike.js new file mode 100644 index 00000000..e5c825a2 --- /dev/null +++ b/src/models/articlelike.js @@ -0,0 +1,67 @@ +export default (sequelize, DataTypes) => { + const ArticleLike = sequelize.define( + 'ArticleLike', + { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: DataTypes.INTEGER + }, + userId: { + type: DataTypes.INTEGER, + allowNull: false, + references: { + model: 'Users', + key: 'id' + }, + onUpdate: 'CASCADE', + onDelete: 'SET NULL' + }, + articleSlug: { + type: DataTypes.STRING, + allowNull: false, + references: { + model: 'Articles', + key: 'slug' + }, + onUpdate: 'CASCADE', + onDelete: 'SET NULL' + }, + status: { + type: DataTypes.ENUM('dislike', 'like'), + allowNull: false + }, + createdAt: { + allowNull: false, + type: DataTypes.DATE + }, + updatedAt: { + allowNull: false, + type: DataTypes.DATE + } + }, + {} + ); + ArticleLike.associate = (models) => { + ArticleLike.belongsTo(models.Article, { + foreignKey: 'articleSlug', + as: 'article' + }); + ArticleLike.belongsTo(models.User, { + foreignKey: 'userId', + as: 'user' + }); + models.Article.belongsToMany(models.User, { + through: models.CommentLike, + foreignKey: 'articleSlug', + otherKey: 'userId' + }); + models.User.belongsToMany(models.Article, { + through: models.CommentLike, + foreignKey: 'userId', + otherKey: 'articleSlug' + }); + }; + return ArticleLike; +}; diff --git a/src/models/chat.js b/src/models/chat.js new file mode 100644 index 00000000..8bbf4160 --- /dev/null +++ b/src/models/chat.js @@ -0,0 +1,46 @@ +export default (sequelize, DataTypes) => { + const Chat = sequelize.define( + 'Chat', + { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: DataTypes.INTEGER + }, + userId: { + type: DataTypes.INTEGER, + allowNull: false, + references: { + model: 'Users', + key: 'id' + }, + onUpdate: 'CASCADE', + onDelete: 'CASCADE' + }, + message: { + type: DataTypes.TEXT, + allowNull: false + }, + chatGroupId: { + type: DataTypes.INTEGER, + allowNull: true, + defaultValue: 1 + }, + createdAt: { + allowNull: false, + type: DataTypes.DATE + }, + updatedAt: { + allowNull: false, + type: DataTypes.DATE + } + }, + {} + ); + Chat.associate = (models) => { + Chat.belongsTo(models.User, { foreignKey: 'userId' }); + Chat.belongsTo(models.ChatGroup, { foreignKey: 'chatGroupId' }); + }; + return Chat; +}; diff --git a/src/models/chatgroup.js b/src/models/chatgroup.js new file mode 100644 index 00000000..c260487e --- /dev/null +++ b/src/models/chatgroup.js @@ -0,0 +1,34 @@ +module.exports = (sequelize, DataTypes) => { + const ChatGroup = sequelize.define( + 'ChatGroup', + { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: DataTypes.INTEGER + }, + name: { + type: DataTypes.STRING, + allowNull: false + }, + createdAt: { + allowNull: false, + type: DataTypes.DATE + }, + updatedAt: { + allowNull: false, + type: DataTypes.DATE + } + }, + {} + ); + ChatGroup.associate = (models) => { + ChatGroup.hasMany(models.Chat, { + foreignKey: 'chatGroupId', + onUpdate: 'CASCADE', + onDelete: 'CASCADE' + }); + }; + return ChatGroup; +}; diff --git a/src/models/chatgroupmember.js b/src/models/chatgroupmember.js new file mode 100644 index 00000000..b4320c5d --- /dev/null +++ b/src/models/chatgroupmember.js @@ -0,0 +1,43 @@ +module.exports = (sequelize, DataTypes) => { + const ChatGroupMember = sequelize.define( + 'ChatGroupMember', + { + chatGroupId: { + primaryKey: true, + type: DataTypes.INTEGER, + allowNull: false, + references: { + model: 'ChatGroups', + key: 'id' + }, + onUpdate: 'CASCADE', + onDelete: 'CASCADE' + }, + userId: { + primaryKey: true, + type: DataTypes.INTEGER, + allowNull: false, + references: { + model: 'Users', + key: 'id' + }, + onUpdate: 'CASCADE', + onDelete: 'CASCADE' + }, + createdAt: { + allowNull: false, + type: DataTypes.DATE + }, + updatedAt: { + allowNull: false, + type: DataTypes.DATE + } + }, + {} + ); + ChatGroupMember.associate = (models) => { + ChatGroupMember.belongsTo(models.User, { foreignKey: 'userId' }); + ChatGroupMember.belongsTo(models.ChatGroup, { foreignKey: 'chatGroupId' }); + }; + return ChatGroupMember; +}; diff --git a/src/models/comment.js b/src/models/comment.js new file mode 100644 index 00000000..a6cdcc0a --- /dev/null +++ b/src/models/comment.js @@ -0,0 +1,63 @@ +import eventEmitter from '../helpers/eventEmitter'; + +export default (sequelize, DataTypes) => { + const Comment = sequelize.define( + 'Comment', + { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: DataTypes.INTEGER + }, + articleSlug: { + type: DataTypes.STRING, + allowNull: false, + references: { + model: 'Articles', + key: 'slug' + }, + onUpdate: 'CASCADE', + onDelete: 'CASCADE' + }, + userId: { + type: DataTypes.INTEGER, + allowNull: true, + references: { + model: 'Users', + key: 'id' + }, + onUpdate: 'CASCADE', + onDelete: 'SET NULL' + }, + body: { + type: DataTypes.TEXT, + allowNull: false + }, + likes: { + allowNull: true, + type: DataTypes.INTEGER + }, + createdAt: { + allowNull: false, + type: DataTypes.DATE + }, + updatedAt: { + allowNull: false, + type: DataTypes.DATE + } + }, + { + hooks: { + afterCreate: async (comment) => { + eventEmitter.emit('commentArticle', comment.get()); + } + } + } + ); + Comment.associate = (models) => { + Comment.belongsTo(models.Article, { foreignKey: 'articleSlug' }); + Comment.belongsTo(models.User, { foreignKey: 'userId', as: 'commentAuthor' }); + }; + return Comment; +}; diff --git a/src/models/commentedit.js b/src/models/commentedit.js new file mode 100644 index 00000000..f13a11a1 --- /dev/null +++ b/src/models/commentedit.js @@ -0,0 +1,56 @@ +export default (sequelize, DataTypes) => { + const CommentEdit = sequelize.define( + 'CommentEdit', + { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: DataTypes.INTEGER + }, + articleSlug: { + type: DataTypes.STRING, + allowNull: false, + references: { + model: 'Articles', + key: 'slug' + }, + onUpdate: 'CASCADE', + onDelete: 'CASCADE' + }, + userId: { + type: DataTypes.INTEGER, + allowNull: true, + references: { + model: 'Users', + key: 'id' + }, + onUpdate: 'CASCADE', + onDelete: 'SET NULL' + }, + body: { + type: DataTypes.TEXT, + allowNull: false + }, + commentId: { + allowNull: null, + type: DataTypes.INTEGER + }, + createdAt: { + allowNull: false, + type: DataTypes.DATE + }, + updatedAt: { + allowNull: false, + type: DataTypes.DATE + } + }, + {} + ); + CommentEdit.associate = (models) => { + CommentEdit.belongsTo(models.User, { foreignKey: 'userId', as: 'user' }); + CommentEdit.belongsTo(models.Article, { foreignKey: 'articleSlug', as: 'article' }); + CommentEdit.belongsTo(models.Comment, { foreignKey: 'commentId', as: 'comment' }); + }; + return CommentEdit; +}; diff --git a/src/models/commentlike.js b/src/models/commentlike.js new file mode 100644 index 00000000..a2b45980 --- /dev/null +++ b/src/models/commentlike.js @@ -0,0 +1,67 @@ +export default (sequelize, DataTypes) => { + const CommentLike = sequelize.define( + 'CommentLike', + { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: DataTypes.INTEGER + }, + userId: { + type: DataTypes.INTEGER, + allowNull: true, + references: { + model: 'Users', + key: 'id' + }, + onUpdate: 'CASCADE', + onDelete: 'SET NULL' + }, + commentId: { + type: DataTypes.INTEGER, + allowNull: true, + references: { + model: 'Comments', + key: 'id' + }, + onUpdate: 'CASCADE', + onDelete: 'SET NULL' + }, + articleSlug: { + type: DataTypes.STRING, + allowNull: false, + references: { + model: 'Articles', + key: 'slug' + }, + onUpdate: 'CASCADE', + onDelete: 'CASCADE' + }, + createdAt: { + allowNull: false, + type: DataTypes.DATE + }, + updatedAt: { + allowNull: false, + type: DataTypes.DATE + } + }, + {} + ); + CommentLike.associate = (models) => { + CommentLike.belongsTo(models.Comment, { foreignKey: 'commentId', as: 'comment' }); + CommentLike.belongsTo(models.User, { foreignKey: 'userId', as: 'user' }); + models.User.belongsToMany(models.Comment, { + through: models.CommentLike, + foreignKey: 'userId', + otherKey: 'commentId' + }); + models.Comment.belongsToMany(models.User, { + through: models.CommentLike, + foreignKey: 'commentId', + otherKey: 'userId' + }); + }; + return CommentLike; +}; diff --git a/src/models/favoritearticle.js b/src/models/favoritearticle.js new file mode 100644 index 00000000..f921d871 --- /dev/null +++ b/src/models/favoritearticle.js @@ -0,0 +1,34 @@ +export default (sequelize, DataTypes) => { + const FavoriteArticle = sequelize.define( + 'FavoriteArticle', + { + userId: { + type: DataTypes.INTEGER, + allowNull: false, + primaryKey: true, + references: { + model: 'Users', + key: 'id' + } + }, + articleSlug: { + type: DataTypes.STRING, + allowNull: false, + primaryKey: true + }, + createdAt: { + allowNull: false, + type: DataTypes.DATE + }, + updatedAt: { + allowNull: false, + type: DataTypes.DATE + } + }, + {} + ); + FavoriteArticle.associate = (models) => { + FavoriteArticle.belongsTo(models.User, { foreignKey: 'userId', as: 'favoritedBy' }); + }; + return FavoriteArticle; +}; diff --git a/src/models/follows.js b/src/models/follows.js new file mode 100644 index 00000000..e65ea1a2 --- /dev/null +++ b/src/models/follows.js @@ -0,0 +1,31 @@ +export default (sequelize, DataTypes) => { + const Follows = sequelize.define( + 'Follows', + { + userId: { + type: DataTypes.INTEGER, + allowNull: false, + primaryKey: true, + references: { + model: 'Users', + key: 'id' + }, + followed: { + type: DataTypes.INTEGER, + allowNull: false, + primaryKey: true, + references: { + model: 'Users', + key: 'id' + } + } + } + }, + {} + ); + Follows.associate = (models) => { + Follows.belongsTo(models.User, { foreignKey: 'userId', as: 'follower' }); + Follows.belongsTo(models.User, { foreignKey: 'followed', as: 'followedUser' }); + }; + return Follows; +}; diff --git a/src/models/gallery.js b/src/models/gallery.js new file mode 100644 index 00000000..3538b3e4 --- /dev/null +++ b/src/models/gallery.js @@ -0,0 +1,40 @@ +export default (sequelize, DataTypes) => { + const Gallery = sequelize.define( + 'Gallery', + { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: DataTypes.INTEGER + }, + userId: { + type: DataTypes.INTEGER, + allowNull: false, + references: { + model: 'Users', + key: 'id' + }, + onUpdate: 'CASCADE', + onDelete: 'CASCADE' + }, + image: { + type: DataTypes.STRING, + allowNull: false + }, + createdAt: { + allowNull: false, + type: DataTypes.DATE + }, + updatedAt: { + allowNull: false, + type: DataTypes.DATE + } + }, + {} + ); + Gallery.associate = (models) => { + Gallery.belongsTo(models.User, { foreignKey: 'userId' }); + }; + return Gallery; +}; diff --git a/src/models/highlight.js b/src/models/highlight.js new file mode 100644 index 00000000..9adc1759 --- /dev/null +++ b/src/models/highlight.js @@ -0,0 +1,63 @@ +module.exports = (sequelize, DataTypes) => { + const Highlight = sequelize.define( + 'Highlight', + { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: DataTypes.INTEGER + }, + articleSlug: { + type: DataTypes.STRING, + allowNull: false + }, + userId: { + type: DataTypes.INTEGER, + allowNull: false, + references: { + model: 'Users', + key: 'id' + }, + onUpdate: 'CASCADE', + onDelete: 'CASCADE' + }, + highlightedText: { + type: DataTypes.TEXT, + allowNull: false, + field: 'highlightedText' + }, + startIndex: { + type: DataTypes.INTEGER, + allowNull: false, + field: 'startIndex' + }, + stopIndex: { + type: DataTypes.INTEGER, + allowNull: false, + field: 'stopIndex' + }, + comment: { + type: DataTypes.TEXT, + allowNull: true + }, + anchorKey: { + type: DataTypes.STRING, + allowNull: true + }, + createdAt: { + allowNull: false, + type: DataTypes.DATE + }, + updatedAt: { + allowNull: false, + type: DataTypes.DATE + } + }, + {} + ); + Highlight.associate = (models) => { + Highlight.belongsTo(models.User, { foreignKey: 'userId', as: 'commentAuthor' }); + }; + return Highlight; +}; diff --git a/src/models/index.js b/src/models/index.js new file mode 100644 index 00000000..d589da06 --- /dev/null +++ b/src/models/index.js @@ -0,0 +1,33 @@ +import fs from 'fs'; +import path from 'path'; +import Sequelize from 'sequelize'; +import dbConfig from '../config/dbConfig'; + +const basename = path.basename(__filename); +const env = process.env.NODE_ENV || 'development'; + +const db = {}; +const config = dbConfig[env]; + +const sequelize = config.use_env_variable + ? new Sequelize(process.env[config.use_env_variable], config) + : new Sequelize(config.database, config.username, config.password, config); + +fs.readdirSync(__dirname) + .filter(file => file.indexOf('.') !== 0 && file !== basename && file.slice(-3) === '.js') + .forEach((file) => { + const model = sequelize.import(path.join(__dirname, file)); + db[model.name] = model; + }); + +Object.keys(db).forEach((modelName) => { + if (db[modelName].associate) { + db[modelName].associate(db); + } +}); + +db.sequelize = sequelize; +db.Sequelize = Sequelize; +db.Op = Sequelize.Op; + +export default db; diff --git a/src/models/notification.js b/src/models/notification.js new file mode 100644 index 00000000..cf1d1610 --- /dev/null +++ b/src/models/notification.js @@ -0,0 +1,54 @@ +export default (sequelize, DataTypes) => { + const Notification = sequelize.define( + 'Notification', + { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: DataTypes.INTEGER + }, + userId: { + type: DataTypes.INTEGER, + allowNull: false, + references: { + model: 'Users', + key: 'id' + }, + onUpdate: 'CASCADE', + onDelete: 'CASCADE' + }, + message: { + type: DataTypes.TEXT, + allowNull: false + }, + preference: { + type: DataTypes.ENUM('inApp', 'email'), + allowNull: true, + defaultValue: 'inApp' + }, + url: { + type: DataTypes.STRING, + allowNull: true + }, + status: { + type: DataTypes.ENUM('seen', 'unseen'), + allowNull: false, + defaultValue: 'unseen' + }, + createdAt: { + allowNull: false, + type: DataTypes.DATE + }, + updatedAt: { + allowNull: false, + type: DataTypes.DATE + } + }, + {} + ); + Notification.associate = (models) => { + Notification.belongsTo(models.User, { foreignKey: 'userId' }); + }; + return Notification; +}; diff --git a/src/models/notificationconfig.js b/src/models/notificationconfig.js new file mode 100644 index 00000000..ce4c7fb0 --- /dev/null +++ b/src/models/notificationconfig.js @@ -0,0 +1,41 @@ +export default (sequelize, DataTypes) => { + const NotificationConfig = sequelize.define( + 'NotificationConfig', + { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: DataTypes.INTEGER + }, + userId: { + type: DataTypes.INTEGER, + allowNull: false, + unique: true, + references: { + model: 'Users', + key: 'id' + }, + onUpdate: 'CASCADE', + onDelete: 'CASCADE' + }, + config: { + type: DataTypes.STRING, + allowNull: false + }, + createdAt: { + allowNull: false, + type: DataTypes.DATE + }, + updatedAt: { + allowNull: false, + type: DataTypes.DATE + } + }, + {} + ); + NotificationConfig.associate = (models) => { + NotificationConfig.belongsTo(models.User, { foreignKey: 'userId' }); + }; + return NotificationConfig; +}; diff --git a/src/models/permission.js b/src/models/permission.js new file mode 100644 index 00000000..dd4d0fd5 --- /dev/null +++ b/src/models/permission.js @@ -0,0 +1,32 @@ +export default (sequelize, DataTypes) => { + const Permission = sequelize.define( + 'Permission', + { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: DataTypes.INTEGER + }, + userType: { + type: DataTypes.STRING, + unique: true, + allowNull: false + }, + permissions: { + type: DataTypes.STRING, + allowNull: false + }, + createdAt: { + allowNull: false, + type: DataTypes.DATE + }, + updatedAt: { + allowNull: false, + type: DataTypes.DATE + } + }, + {} + ); + return Permission; +}; diff --git a/src/models/rating.js b/src/models/rating.js new file mode 100644 index 00000000..0696b3c7 --- /dev/null +++ b/src/models/rating.js @@ -0,0 +1,54 @@ +export default (sequelize, DataTypes) => { + const Rating = sequelize.define( + 'Rating', + { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: DataTypes.INTEGER + }, + userId: { + type: DataTypes.INTEGER, + allowNull: false, + references: { + model: 'Users', + key: 'id' + }, + onUpdate: 'CASCADE', + onDelete: 'CASCADE' + }, + articleId: { + type: DataTypes.INTEGER, + allowNull: false, + references: { + model: 'Articles', + key: 'id' + }, + onUpdate: 'CASCADE', + onDelete: 'CASCADE' + }, + rating: { + type: DataTypes.INTEGER, + allowNull: false + }, + createdAt: { + allowNull: false, + type: DataTypes.DATE + }, + updatedAt: { + allowNull: false, + type: DataTypes.DATE + } + }, + {} + ); + Rating.associate = (models) => { + Rating.belongsTo(models.Article, { + foreignKey: 'articleId', + onDelete: 'CASCADE' + }); + Rating.belongsTo(models.User, { foreignKey: 'userId', as: 'author' }); + }; + return Rating; +}; diff --git a/src/models/readingstat.js b/src/models/readingstat.js new file mode 100644 index 00000000..fded726d --- /dev/null +++ b/src/models/readingstat.js @@ -0,0 +1,48 @@ +module.exports = (sequelize, DataTypes) => { + const ReadingStat = sequelize.define( + 'ReadingStat', + { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: DataTypes.INTEGER + }, + userId: { + type: DataTypes.INTEGER, + allowNull: false, + references: { + model: 'Users', + key: 'id' + }, + onUpdate: 'CASCADE', + onDelete: 'CASCADE' + }, + articleSlug: { + type: DataTypes.STRING, + allowNull: false + }, + createdAt: { + allowNull: false, + type: DataTypes.DATE + }, + updatedAt: { + allowNull: false, + type: DataTypes.DATE + } + }, + {} + ); + ReadingStat.associate = (models) => { + ReadingStat.belongsTo(models.User, { + foreignKey: 'userId', + as: 'user' + }); + ReadingStat.belongsTo(models.Article, { + foreignKey: 'articleSlug', + targetKey: 'slug', + as: 'article' + }); + }; + return ReadingStat; +}; diff --git a/src/models/report.js b/src/models/report.js new file mode 100644 index 00000000..c3573d5a --- /dev/null +++ b/src/models/report.js @@ -0,0 +1,60 @@ +export default (sequelize, DataTypes) => { + const Report = sequelize.define( + 'Report', + { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: DataTypes.INTEGER + }, + articleSlug: { + type: DataTypes.STRING, + allowNull: false, + references: { + model: 'Articles', + key: 'slug' + }, + onUpdate: 'CASCADE', + onDelete: 'CASCADE' + }, + userId: { + type: DataTypes.INTEGER, + allowNull: true, + references: { + model: 'Users', + key: 'id' + }, + onUpdate: 'CASCADE', + onDelete: 'SET NULL' + }, + title: { + type: DataTypes.STRING, + allowNull: false + }, + + body: { + type: DataTypes.TEXT, + allowNull: false + }, + type: { + allowNull: true, + type: DataTypes.ARRAY(DataTypes.STRING) + }, + createdAt: { + allowNull: false, + type: DataTypes.DATE + }, + updatedAt: { + allowNull: false, + type: DataTypes.DATE + } + }, + {} + ); + Report.associate = (models) => { + Report.belongsTo(models.User, { foreignKey: 'userId', as: 'reporter' }); + Report.belongsTo(models.Article, { foreignKey: 'articleSlug', as: 'article' }); + }; + return Report; +}; diff --git a/src/models/token.js b/src/models/token.js new file mode 100644 index 00000000..fb0ca2d7 --- /dev/null +++ b/src/models/token.js @@ -0,0 +1,40 @@ +export default (sequelize, DataTypes) => { + const Token = sequelize.define( + 'Token', + { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: DataTypes.INTEGER + }, + userId: { + type: DataTypes.INTEGER, + allowNull: false, + references: { + model: 'Users', + key: 'id' + }, + onUpdate: 'CASCADE', + onDelete: 'CASCADE' + }, + token: { + type: DataTypes.STRING, + allowNull: false + }, + createdAt: { + allowNull: false, + type: DataTypes.DATE + }, + updatedAt: { + allowNull: false, + type: DataTypes.DATE + } + }, + {} + ); + Token.associate = (models) => { + Token.belongsTo(models.User, { foreignKey: 'userId' }); + }; + return Token; +}; diff --git a/src/models/user.js b/src/models/user.js new file mode 100644 index 00000000..fa97e800 --- /dev/null +++ b/src/models/user.js @@ -0,0 +1,101 @@ +import dotenv from 'dotenv'; + +dotenv.config(); + +export default (sequelize, DataTypes) => { + const User = sequelize.define( + 'User', + { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: DataTypes.INTEGER + }, + firstName: { + type: DataTypes.STRING, + allowNull: false + }, + lastName: { + type: DataTypes.STRING, + allowNull: false + }, + username: { + type: DataTypes.STRING, + allowNull: true, + unique: true + }, + email: { + type: DataTypes.STRING, + allowNull: true, + unique: true + }, + password: { + type: DataTypes.STRING, + allowNull: true + }, + bio: { + type: DataTypes.TEXT, + allowNull: true + }, + image: { + type: DataTypes.STRING, + allowNull: true + }, + role: { + type: DataTypes.ENUM('normal', 'admin'), + allowNull: false, + defaultValue: 'normal' + }, + permissions: { + type: DataTypes.STRING, + allowNull: true + }, + isActive: { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: false + }, + accountProvider: { + type: DataTypes.ENUM('facebook', 'twitter', 'google'), + allowNull: true + }, + accountProviderUserId: { + type: DataTypes.STRING, + allowNull: true + }, + createdAt: { + allowNull: false, + type: DataTypes.DATE + }, + updatedAt: { + allowNull: false, + type: DataTypes.DATE + } + }, + {} + ); + User.associate = (models) => { + User.hasMany(models.Article, { + foreignKey: 'userId', + onUpdate: 'CASCADE', + onDelete: 'CASCADE' + }); + User.hasMany(models.Comment, { + foreignKey: 'userId', + onUpdate: 'CASCADE', + onDelete: 'CASCADE' + }); + User.hasOne(models.Token, { + foreignKey: 'userId', + onUpdate: 'CASCADE', + onDelete: 'CASCADE' + }); + User.hasMany(models.ArticleBookmark, { + foreignKey: 'userId', + onUpdate: 'CASCADE', + onDelete: 'CASCADE' + }); + }; + return User; +}; diff --git a/src/queries/articleBookmarks/bookmarkArticle.js b/src/queries/articleBookmarks/bookmarkArticle.js new file mode 100644 index 00000000..5bfa2277 --- /dev/null +++ b/src/queries/articleBookmarks/bookmarkArticle.js @@ -0,0 +1,21 @@ +import db from '../../models'; + +/** + * @param {int} userId + * @param {string} articleSlug + * @returns {object} returns the user Id and the slug of the article in a object + */ +export default async (userId, articleSlug) => { + try { + const bookmarkedArticle = await db.ArticleBookmark.create( + { userId, articleSlug }, + { logging: false } + ); + + return bookmarkedArticle.get(); + } catch (error) { + return { + errors: error + }; + } +}; diff --git a/src/queries/articleBookmarks/getAllBookmarkedArticles.js b/src/queries/articleBookmarks/getAllBookmarkedArticles.js new file mode 100644 index 00000000..18dcb68f --- /dev/null +++ b/src/queries/articleBookmarks/getAllBookmarkedArticles.js @@ -0,0 +1,29 @@ +import db from '../../models'; + +/** + * @param {int} bookmarkedBy + * @returns {array} an array containing the list of bookmarked articles + */ +export default async (bookmarkedBy) => { + try { + const bookmarkedArticles = bookmarkedBy + ? await db.ArticleBookmark.findAll({ + where: { userId: bookmarkedBy }, + logging: false, + include: [ + { + model: db.Article, + as: 'article', + attributes: ['id', 'title', 'description', 'coverUrl', 'readTime'] + } + ] + }) + : null; + + return bookmarkedArticles || []; + } catch (error) { + return { + errors: error + }; + } +}; diff --git a/src/queries/articleBookmarks/index.js b/src/queries/articleBookmarks/index.js new file mode 100644 index 00000000..5742ccd9 --- /dev/null +++ b/src/queries/articleBookmarks/index.js @@ -0,0 +1,5 @@ +import add from './bookmarkArticle'; +import getAll from './getAllBookmarkedArticles'; +import remove from './removeBookmarkedArticle'; + +export { add, getAll, remove }; diff --git a/src/queries/articleBookmarks/removeBookmarkedArticle.js b/src/queries/articleBookmarks/removeBookmarkedArticle.js new file mode 100644 index 00000000..6fa39c5f --- /dev/null +++ b/src/queries/articleBookmarks/removeBookmarkedArticle.js @@ -0,0 +1,20 @@ +import db from '../../models'; + +/** + * @param {int} userId + * @param {string} articleSlug + * @returns {int} returns 1 if the bookmark was removed otherwise 0 + */ +export default async (userId, articleSlug) => { + try { + const removedBookmark = userId && articleSlug + ? await db.ArticleBookmark.destroy({ where: { userId, articleSlug }, logging: false }) + : null; + + return removedBookmark; + } catch (error) { + return { + errors: error + }; + } +}; diff --git a/src/queries/articles/create.js b/src/queries/articles/create.js new file mode 100644 index 00000000..63840254 --- /dev/null +++ b/src/queries/articles/create.js @@ -0,0 +1,10 @@ +import db from '../../models'; + +/** + * @param {object} data inputs data to be saved in db + * @returns {object} Object representing the response returned + */ +export default async (data) => { + const response = await db.Article.create(data, { logging: false }); + return response; +}; diff --git a/src/queries/articles/deleteArticle.js b/src/queries/articles/deleteArticle.js new file mode 100644 index 00000000..7bb1c2b4 --- /dev/null +++ b/src/queries/articles/deleteArticle.js @@ -0,0 +1,16 @@ +import db from '../../models'; + +/** + * Publish article + * @param {object} slug unique slug for article to be deleted + * @returns {object} Object representing the response returned + */ +export default async (slug) => { + const response = await db.Article.update( + { + status: 'deleted' + }, + { where: { slug }, logging: false } + ); + return response; +}; diff --git a/src/queries/articles/get.js b/src/queries/articles/get.js new file mode 100644 index 00000000..2bd509d1 --- /dev/null +++ b/src/queries/articles/get.js @@ -0,0 +1,20 @@ +import Sequelize from 'sequelize'; +import db from '../../models'; + +const { Op } = Sequelize; +/** + * Get specific article + * @param {object} condition condition for query + * @returns {object} Object representing the response returned + */ +export default async (condition = {}) => db.Article.findOne({ + where: { ...condition, status: { [Op.ne]: 'deleted' } }, + logging: false, + include: [ + { + model: db.User, + as: 'author', + attributes: ['username', 'firstName', 'lastName', 'bio', 'image'] + } + ] +}); diff --git a/src/queries/articles/getAll.js b/src/queries/articles/getAll.js new file mode 100644 index 00000000..f04fa43c --- /dev/null +++ b/src/queries/articles/getAll.js @@ -0,0 +1,29 @@ +import db from '../../models'; +import * as filters from '../../helpers/searchArticleFilters'; + +/** + * Get specific article + * @param {object} limit limit for query + * @param {object} offset offset for query + * @param {object} condition contains author, tag and keyword to filter query + * @returns {object} Object representing the response returned + */ +export default async (limit, offset, condition = {}) => { + const where = filters.filterQueryBuilder(condition); + let response = []; + response = await db.Article.findAll({ + limit, + offset, + where, + order: [['id', 'DESC']], + logging: false, + include: [ + { + model: db.User, + as: 'author', + attributes: ['firstName', 'lastName', 'username', 'bio', 'image'] + } + ] + }); + return response; +}; diff --git a/src/queries/articles/getArticlesCounter.js b/src/queries/articles/getArticlesCounter.js new file mode 100644 index 00000000..d545302e --- /dev/null +++ b/src/queries/articles/getArticlesCounter.js @@ -0,0 +1,17 @@ +import db from '../../models'; + +/** + * Get specific article + * @param {object} status limit for query + * @param {object} offset offset for query + * @param {object} condition contains author, tag and keyword to filter query + * @returns {object} Object representing the response returned + */ +export default async (status) => { + let response = []; + response = await db.Article.count({ + where: { status }, + logging: false + }); + return response; +}; diff --git a/src/queries/articles/getUserArticles.js b/src/queries/articles/getUserArticles.js new file mode 100644 index 00000000..00193e04 --- /dev/null +++ b/src/queries/articles/getUserArticles.js @@ -0,0 +1,21 @@ +import db from '../../models'; + +/** + * Get specific article + * @param {object} limit limit for query + * @param {object} offset offset for query + * @param {object} condition contains author, tag and keyword to filter query + * @returns {object} Object representing the response returned + */ +export default async (limit, offset, condition = {}) => { + const { userId, status } = condition; + let response = []; + response = await db.Article.findAll({ + limit, + offset, + where: { userId, status }, + order: [['id', 'DESC']], + logging: false + }); + return response; +}; diff --git a/src/queries/articles/index.js b/src/queries/articles/index.js new file mode 100644 index 00000000..9de3ada2 --- /dev/null +++ b/src/queries/articles/index.js @@ -0,0 +1,17 @@ +import create from './create'; +import update from './update'; +import get from './get'; +import getAll from './getAll'; +import * as bookmark from '../articleBookmarks'; +import * as favorite from '../favoriteArticles'; +import getUserArticles from './getUserArticles'; +import updateCover from './updateCover'; +import * as rate from '../ratings'; +import getArticlesCounter from './getArticlesCounter'; + +export { + create, update, favorite, bookmark +}; +export { + get, getAll, getUserArticles, updateCover, rate, getArticlesCounter +}; diff --git a/src/queries/articles/likes/create.js b/src/queries/articles/likes/create.js new file mode 100644 index 00000000..0561d286 --- /dev/null +++ b/src/queries/articles/likes/create.js @@ -0,0 +1,5 @@ +import db from '../../../models'; +import { dbCreate } from '../../../helpers/queryHelper'; + +const create = async condition => dbCreate(db.ArticleLike, condition); +export default create; diff --git a/src/queries/articles/likes/deleteLike.js b/src/queries/articles/likes/deleteLike.js new file mode 100644 index 00000000..bf8f9119 --- /dev/null +++ b/src/queries/articles/likes/deleteLike.js @@ -0,0 +1,6 @@ +import db from '../../../models'; +import { dbDelete } from '../../../helpers/queryHelper'; + +const deleteLike = async condition => dbDelete(db.ArticleLike, condition); + +export default deleteLike; diff --git a/src/queries/articles/likes/getAllLikes.js b/src/queries/articles/likes/getAllLikes.js new file mode 100644 index 00000000..38c98195 --- /dev/null +++ b/src/queries/articles/likes/getAllLikes.js @@ -0,0 +1,6 @@ +import db from '../../../models'; +import { dbFindAll } from '../../../helpers/queryHelper'; + +const getAllLikes = async condition => dbFindAll(db.ArticleLike, condition); + +export default getAllLikes; diff --git a/src/queries/articles/likes/getSingleLike.js b/src/queries/articles/likes/getSingleLike.js new file mode 100644 index 00000000..f3d1b3e2 --- /dev/null +++ b/src/queries/articles/likes/getSingleLike.js @@ -0,0 +1,5 @@ +import db from '../../../models'; +import { dbFindSingle } from '../../../helpers/queryHelper'; + +const getSingleLike = async condition => dbFindSingle(db.ArticleLike, condition); +export default getSingleLike; diff --git a/src/queries/articles/likes/index.js b/src/queries/articles/likes/index.js new file mode 100644 index 00000000..0166aff4 --- /dev/null +++ b/src/queries/articles/likes/index.js @@ -0,0 +1,9 @@ +import create from './create'; +import deleteLike from './deleteLike'; +import getAllLikes from './getAllLikes'; +import getSingleLike from './getSingleLike'; +import updateLike from './updateLike'; + +export { + create, deleteLike, getAllLikes, getSingleLike, updateLike +}; diff --git a/src/queries/articles/likes/updateLike.js b/src/queries/articles/likes/updateLike.js new file mode 100644 index 00000000..a0adea4f --- /dev/null +++ b/src/queries/articles/likes/updateLike.js @@ -0,0 +1,6 @@ +/* eslint-disable max-len */ +import db from '../../../models'; + +const updateLike = async (condition = {}, whereCondition) => db.ArticleLike.update(condition, { where: whereCondition, logging: false }); + +export default updateLike; diff --git a/src/queries/articles/update.js b/src/queries/articles/update.js new file mode 100644 index 00000000..da6a0933 --- /dev/null +++ b/src/queries/articles/update.js @@ -0,0 +1,14 @@ +import db from '../../models'; +/** + * @param {object} data inputs data to be saved in db + * @param {object} slug unique slug to update article + * @returns {object} Object representing the response returned + */ +export default async (data = {}, slug = '') => { + const response = await db.Article.update(data, { + where: { slug }, + logging: false, + individualHooks: true + }); + return response[0] ? response[1][0].get() : {}; +}; diff --git a/src/queries/articles/updateArticleLikes.js b/src/queries/articles/updateArticleLikes.js new file mode 100644 index 00000000..f8573215 --- /dev/null +++ b/src/queries/articles/updateArticleLikes.js @@ -0,0 +1,15 @@ +import * as like from './likes'; +import * as article from '.'; + +const updateArticleLikes = async (req) => { + const { articleSlug } = req.params; + + const findAllLikes = await like.getAllLikes({ status: 'like', articleSlug }); + const findAllDislikes = await like.getAllLikes({ status: 'dislike', articleSlug }); + + await article.update( + { likes: findAllLikes.length, dislikes: findAllDislikes.length }, + req.params.articleSlug + ); +}; +export default updateArticleLikes; diff --git a/src/queries/articles/updateCover.js b/src/queries/articles/updateCover.js new file mode 100644 index 00000000..88958dba --- /dev/null +++ b/src/queries/articles/updateCover.js @@ -0,0 +1,12 @@ +import db from '../../models'; +// import status from '../../config/status'; +/** + * @param {object} data inputs data to be saved in db + * @param {object} slug unique slug to update article + * @param {object} userId UserId + * @returns {object} Object representing the response returned + */ +export default async (data = {}, slug, userId) => { + const response = await db.Article.update(data, { where: { slug, userId } }); + return response; +}; diff --git a/src/queries/chats/getAllChats.js b/src/queries/chats/getAllChats.js new file mode 100644 index 00000000..6e427027 --- /dev/null +++ b/src/queries/chats/getAllChats.js @@ -0,0 +1,28 @@ +import db from '../../models'; + +/** + * @param {int} offset offset for query + * @param {int} limit limit for query + * @returns {object} Object representing the response returned + */ +export default async (offset = 0, limit = 20) => { + try { + const chats = await db.Chat.findAll({ + limit, + offset, + order: [['id', 'DESC']], + logging: false, + include: [ + { + model: db.User, + attributes: ['firstName', 'lastName', 'username', 'email', 'image'] + } + ] + }); + return chats; + } catch (error) { + return { + errors: error + }; + } +}; diff --git a/src/queries/chats/index.js b/src/queries/chats/index.js new file mode 100644 index 00000000..261b374b --- /dev/null +++ b/src/queries/chats/index.js @@ -0,0 +1,5 @@ +import save from './saveChat'; +import getAll from './getAllChats'; +import remove from './removeChat'; + +export { save, getAll, remove }; diff --git a/src/queries/chats/removeChat.js b/src/queries/chats/removeChat.js new file mode 100644 index 00000000..339d7391 --- /dev/null +++ b/src/queries/chats/removeChat.js @@ -0,0 +1,20 @@ +import db from '../../models'; + +/** + * @param {int} chatId the id of the chat + * @param {int} userId the id of the user + * @returns {int} return the number of affected rows + */ +export default async (chatId = 0, userId) => { + try { + const removedChat = userId + ? await db.Chat.destroy({ where: { id: chatId, userId }, logging: false }) + : await db.Chat.destroy({ where: { id: chatId }, logging: false }); + + return removedChat; + } catch (error) { + return { + errors: error + }; + } +}; diff --git a/src/queries/chats/saveChat.js b/src/queries/chats/saveChat.js new file mode 100644 index 00000000..2e1613e2 --- /dev/null +++ b/src/queries/chats/saveChat.js @@ -0,0 +1,18 @@ +import db from '../../models'; + +/** + * @param {int} userId + * @param {string} message + * @param {int} chatGroupId + * @returns {object} return the saved chat + */ +export default async (userId, message, chatGroupId = 1) => { + try { + const newChat = await db.Chat.create({ userId, message, chatGroupId }, { logging: false }); + return newChat.get(); + } catch (error) { + return { + errors: error + }; + } +}; diff --git a/src/queries/comments/create.js b/src/queries/comments/create.js new file mode 100644 index 00000000..16f1f83e --- /dev/null +++ b/src/queries/comments/create.js @@ -0,0 +1,6 @@ +import db from '../../models'; +import { dbCreate } from '../../helpers/queryHelper'; + +const create = async data => dbCreate(db.Comment, data); + +export default create; diff --git a/src/queries/comments/edits/create.js b/src/queries/comments/edits/create.js new file mode 100644 index 00000000..cf20a1cd --- /dev/null +++ b/src/queries/comments/edits/create.js @@ -0,0 +1,6 @@ +import db from '../../../models'; +import { dbCreate } from '../../../helpers/queryHelper'; + +const create = async data => dbCreate(db.CommentEdit, data); + +export default create; diff --git a/src/queries/comments/edits/findOneComment.js b/src/queries/comments/edits/findOneComment.js new file mode 100644 index 00000000..bf929ebe --- /dev/null +++ b/src/queries/comments/edits/findOneComment.js @@ -0,0 +1,5 @@ +import * as comment from '../index'; + +// eslint-disable-next-line max-len +const findOneComment = async (articleSlug, commentId, userId) => comment.getSingleComment({ articleSlug, commentId, userId }); +export default findOneComment; diff --git a/src/queries/comments/edits/getAll.js b/src/queries/comments/edits/getAll.js new file mode 100644 index 00000000..eee52a40 --- /dev/null +++ b/src/queries/comments/edits/getAll.js @@ -0,0 +1,5 @@ +import db from '../../../models'; +import { dbFindAll } from '../../../helpers/queryHelper'; + +const getAll = async condition => dbFindAll(db.CommentEdit, condition); +export default getAll; diff --git a/src/queries/comments/edits/getSingle.js b/src/queries/comments/edits/getSingle.js new file mode 100644 index 00000000..137c159c --- /dev/null +++ b/src/queries/comments/edits/getSingle.js @@ -0,0 +1,5 @@ +import db from '../../../models'; +import { dbFindSingle } from '../../../helpers/queryHelper'; + +const getSingle = async (condition = {}) => dbFindSingle(db.CommentEdit, condition); +export default getSingle; diff --git a/src/queries/comments/edits/index.js b/src/queries/comments/edits/index.js new file mode 100644 index 00000000..c809200b --- /dev/null +++ b/src/queries/comments/edits/index.js @@ -0,0 +1,8 @@ +import getAll from './getAll'; +import getSingle from './getSingle'; +import create from './create'; +import remove from './remove'; + +export { + getAll, getSingle, create, remove +}; diff --git a/src/queries/comments/edits/remove.js b/src/queries/comments/edits/remove.js new file mode 100644 index 00000000..93ee41a4 --- /dev/null +++ b/src/queries/comments/edits/remove.js @@ -0,0 +1,5 @@ +import db from '../../../models'; +import { dbDelete } from '../../../helpers/queryHelper'; + +const remove = async (condition = {}) => dbDelete(db.CommentEdit, condition); +export default remove; diff --git a/src/queries/comments/getAll.js b/src/queries/comments/getAll.js new file mode 100644 index 00000000..03894433 --- /dev/null +++ b/src/queries/comments/getAll.js @@ -0,0 +1,12 @@ +import db from '../../models'; +import { dbFindAll } from '../../helpers/queryHelper'; + +const getAll = async (data = {}) => dbFindAll(db.Comment, data, null, null, [ + { + model: db.User, + attributes: ['firstName', 'lastName', 'username', 'email', 'image'], + as: 'commentAuthor' + } +]); + +export default getAll; diff --git a/src/queries/comments/getSingle.js b/src/queries/comments/getSingle.js new file mode 100644 index 00000000..d5cdd397 --- /dev/null +++ b/src/queries/comments/getSingle.js @@ -0,0 +1,12 @@ +import db from '../../models'; +import { dbFindSingle } from '../../helpers/queryHelper'; + +const getSingle = async (condition = {}) => dbFindSingle(db.Comment, condition, [ + { + model: db.User, + as: 'commentAuthor', + attributes: ['firstName', 'lastName', 'username', 'email', 'image'] + } +]); + +export default getSingle; diff --git a/src/queries/comments/index.js b/src/queries/comments/index.js new file mode 100644 index 00000000..c6731945 --- /dev/null +++ b/src/queries/comments/index.js @@ -0,0 +1,9 @@ +import create from './create'; +import remove from './remove'; +import getAll from './getAll'; +import getSingle from './getSingle'; +import update from './update'; + +export { + create, remove, getAll, getSingle, update +}; diff --git a/src/queries/comments/likes/createLike.js b/src/queries/comments/likes/createLike.js new file mode 100644 index 00000000..f909ab73 --- /dev/null +++ b/src/queries/comments/likes/createLike.js @@ -0,0 +1,5 @@ +import db from '../../../models'; +import { dbCreate } from '../../../helpers/queryHelper'; + +const createLike = async condition => dbCreate(db.CommentLike, condition); +export default createLike; diff --git a/src/queries/comments/likes/deleteLike.js b/src/queries/comments/likes/deleteLike.js new file mode 100644 index 00000000..9c45a87e --- /dev/null +++ b/src/queries/comments/likes/deleteLike.js @@ -0,0 +1,6 @@ +import db from '../../../models'; +import { dbDelete } from '../../../helpers/queryHelper'; + +const deleteLike = async condition => dbDelete(db.CommentLike, condition); + +export default deleteLike; diff --git a/src/queries/comments/likes/getAllLikes.js b/src/queries/comments/likes/getAllLikes.js new file mode 100644 index 00000000..8ce5c3fe --- /dev/null +++ b/src/queries/comments/likes/getAllLikes.js @@ -0,0 +1,6 @@ +import db from '../../../models'; +import { dbFindAll } from '../../../helpers/queryHelper'; + +const getAllLikes = async condition => dbFindAll(db.CommentLike, condition); + +export default getAllLikes; diff --git a/src/queries/comments/likes/getSingleLike.js b/src/queries/comments/likes/getSingleLike.js new file mode 100644 index 00000000..c1bdeef5 --- /dev/null +++ b/src/queries/comments/likes/getSingleLike.js @@ -0,0 +1,5 @@ +import db from '../../../models'; +import { dbFindSingle } from '../../../helpers/queryHelper'; + +const getSingleLike = async condition => dbFindSingle(db.CommentLike, condition); +export default getSingleLike; diff --git a/src/queries/comments/likes/index.js b/src/queries/comments/likes/index.js new file mode 100644 index 00000000..b95cccf2 --- /dev/null +++ b/src/queries/comments/likes/index.js @@ -0,0 +1,8 @@ +import createLike from './createLike'; +import deleteLike from './deleteLike'; +import getAllLikes from './getAllLikes'; +import getSingleLike from './getSingleLike'; + +export { + createLike, deleteLike, getAllLikes, getSingleLike +}; diff --git a/src/queries/comments/remove.js b/src/queries/comments/remove.js new file mode 100644 index 00000000..ad596141 --- /dev/null +++ b/src/queries/comments/remove.js @@ -0,0 +1,5 @@ +import db from '../../models'; +import { dbDelete } from '../../helpers/queryHelper'; + +const remove = async (condition = {}) => dbDelete(db.Comment, condition); +export default remove; diff --git a/src/queries/comments/update.js b/src/queries/comments/update.js new file mode 100644 index 00000000..7a5f399f --- /dev/null +++ b/src/queries/comments/update.js @@ -0,0 +1,7 @@ +import db from '../../models'; +import { dbUpdate } from '../../helpers/queryHelper'; + +// eslint-disable-next-line max-len +const updateComment = async (condition, whereCondition) => dbUpdate(db.Comment, condition, whereCondition); + +export default updateComment; diff --git a/src/queries/comments/updateCommentLikes.js b/src/queries/comments/updateCommentLikes.js new file mode 100644 index 00000000..0d6e7b2d --- /dev/null +++ b/src/queries/comments/updateCommentLikes.js @@ -0,0 +1,8 @@ +import * as comment from '.'; +import * as likes from './likes'; + +export default async (req) => { + const { commentId, articleSlug } = req.params; + const findAllLikes = await likes.getAllLikes({ commentId, articleSlug }); + await comment.update({ likes: findAllLikes.length }, { id: commentId }); +}; diff --git a/src/queries/favoriteArticles/favoriteArticle.js b/src/queries/favoriteArticles/favoriteArticle.js new file mode 100644 index 00000000..eb3c2032 --- /dev/null +++ b/src/queries/favoriteArticles/favoriteArticle.js @@ -0,0 +1,24 @@ +import db from '../../models'; +import updateArticle from '../articles/update'; +/** + * @param {int} userId + * @param {string} articleSlug + * @param {string} favoritesCount + * @returns {object} returns the user Id and the slug of the article in a object + */ +export default async (userId, articleSlug, favoritesCount) => { + try { + const favorite = (await db.FavoriteArticle.create( + { userId, articleSlug }, + { logging: false } + )).get(); + if (Object.keys(favorite).length) { + await updateArticle({ favoritesCount: favoritesCount + 1 }, articleSlug); + } + return favorite; + } catch (error) { + return { + errors: error + }; + } +}; diff --git a/src/queries/favoriteArticles/getAllFavoritedArticles.js b/src/queries/favoriteArticles/getAllFavoritedArticles.js new file mode 100644 index 00000000..26e7197b --- /dev/null +++ b/src/queries/favoriteArticles/getAllFavoritedArticles.js @@ -0,0 +1,34 @@ +import db from '../../models'; + +/** + * @param {int} favoritedBy + * @param {string} articleSlug + * @returns {array} an array containing the list of favorited articles + */ +export default async (favoritedBy, articleSlug) => { + try { + const condition = { + userId: favoritedBy, + articleSlug + }; + Object.keys(condition).forEach(key => condition[key] || delete condition[key]); + + return Object.keys(condition).length + ? await db.FavoriteArticle.findAll({ + where: condition, + logging: false, + include: [ + { + model: db.User, + as: 'favoritedBy', + attributes: ['id', 'firstName', 'lastName', 'username', 'email', 'image'] + } + ] + }) + : []; + } catch (error) { + return { + errors: error + }; + } +}; diff --git a/src/queries/favoriteArticles/index.js b/src/queries/favoriteArticles/index.js new file mode 100644 index 00000000..862304be --- /dev/null +++ b/src/queries/favoriteArticles/index.js @@ -0,0 +1,5 @@ +import add from './favoriteArticle'; +import getAll from './getAllFavoritedArticles'; +import remove from './removeFavoritedArticle'; + +export { add, getAll, remove }; diff --git a/src/queries/favoriteArticles/removeFavoritedArticle.js b/src/queries/favoriteArticles/removeFavoritedArticle.js new file mode 100644 index 00000000..d513a7a3 --- /dev/null +++ b/src/queries/favoriteArticles/removeFavoritedArticle.js @@ -0,0 +1,25 @@ +import db from '../../models'; +import updateArticle from '../articles/update'; + +/** + * @param {int} userId + * @param {string} articleSlug + * @param {string} favoritesCount + * @returns {int} returns 1 if the favorited article was removed otherwise 0 + */ +export default async (userId, articleSlug, favoritesCount) => { + try { + const removeFavorite = await db.FavoriteArticle.destroy({ + where: { userId, articleSlug }, + logging: false + }); + if (removeFavorite) { + await updateArticle({ favoritesCount: favoritesCount - 1 }, articleSlug); + } + return removeFavorite; + } catch (error) { + return { + errors: error + }; + } +}; diff --git a/src/queries/follows/followUser.js b/src/queries/follows/followUser.js new file mode 100644 index 00000000..34cba5dd --- /dev/null +++ b/src/queries/follows/followUser.js @@ -0,0 +1,28 @@ +import db from '../../models'; + +/** + * @param {object} object + * @returns {object} an object containing the information of the user or null + */ +export default async (object) => { + try { + const followUser = await db.Follows.create( + { followed: object.followed, userId: object.userId }, + { + logging: false, + include: [ + { + model: db.User, + as: 'followedUser', + attributes: ['id', 'firstName', 'lastName', 'username', 'email', 'image'] + } + ] + } + ); + return followUser.dataValues; + } catch (error) { + return { + errors: error + }; + } +}; diff --git a/src/queries/follows/getAllFollowers.js b/src/queries/follows/getAllFollowers.js new file mode 100644 index 00000000..3065b48e --- /dev/null +++ b/src/queries/follows/getAllFollowers.js @@ -0,0 +1,25 @@ +import db from '../../models'; + +/** + * @param {object} values + * @returns {object} an object the return users + */ +export default async (values) => { + const user = await db.Follows.findAll({ + where: values, + logging: false, + include: [ + { + model: db.User, + as: 'follower', + attributes: ['id', 'firstName', 'lastName', 'username', 'email', 'image'] + }, + { + model: db.User, + as: 'followedUser', + attributes: ['id', 'firstName', 'lastName', 'username', 'email', 'image'] + } + ] + }); + return user; +}; diff --git a/src/queries/follows/index.js b/src/queries/follows/index.js new file mode 100644 index 00000000..ca096ae8 --- /dev/null +++ b/src/queries/follows/index.js @@ -0,0 +1,5 @@ +import add from './followUser'; +import remove from './unFollowUser'; +import getAll from './getAllFollowers'; + +export { add, remove, getAll }; diff --git a/src/queries/follows/unFollowUser.js b/src/queries/follows/unFollowUser.js new file mode 100644 index 00000000..31a0d0d0 --- /dev/null +++ b/src/queries/follows/unFollowUser.js @@ -0,0 +1,16 @@ +import db from '../../models'; + +/** + * @param {object} input user input, userId and user to unfollow + * @returns {object} an object containing the information of the user or null + */ +export default async (input = {}) => { + try { + const followUser = await db.Follows.destroy({ where: input }, { logging: false }); + return followUser; + } catch (error) { + return { + errors: error.message + }; + } +}; diff --git a/src/queries/gallery/get.js b/src/queries/gallery/get.js new file mode 100644 index 00000000..6c3baecf --- /dev/null +++ b/src/queries/gallery/get.js @@ -0,0 +1,21 @@ +import db from '../../models'; + +/** + * Get specific article + * @param {object} limit limit for query + * @param {object} offset offset for query + * @param {object} userId User to identify the gallery + * @returns {object} Object representing the response returned + */ +export default async (limit, offset, userId) => { + let response = []; + response = await db.Gallery.findAll({ + limit, + offset, + where: userId, + order: [['id', 'DESC']], + attributes: ['id', 'image', 'createdAt'], + logging: false + }); + return response; +}; diff --git a/src/queries/gallery/index.js b/src/queries/gallery/index.js new file mode 100644 index 00000000..46516c4d --- /dev/null +++ b/src/queries/gallery/index.js @@ -0,0 +1,4 @@ +import get from './get'; +import save from './save'; + +export { get, save }; diff --git a/src/queries/gallery/save.js b/src/queries/gallery/save.js new file mode 100644 index 00000000..a43b6fa6 --- /dev/null +++ b/src/queries/gallery/save.js @@ -0,0 +1,10 @@ +import db from '../../models'; + +/** + * @param {object} data inputs data to be saved in db + * @returns {object} Object representing the response returned + */ +export default async (data) => { + const response = await db.Gallery.create(data, { logging: false }); + return response; +}; diff --git a/src/queries/highlights/deleteHighlight.js b/src/queries/highlights/deleteHighlight.js new file mode 100644 index 00000000..52827fb4 --- /dev/null +++ b/src/queries/highlights/deleteHighlight.js @@ -0,0 +1,6 @@ +import db from '../../models'; +import { dbDelete } from '../../helpers/queryHelper'; + +const deleteHighlight = async (condition = {}) => dbDelete(db.Highlight, condition); + +export default deleteHighlight; diff --git a/src/queries/highlights/findOrCreate.js b/src/queries/highlights/findOrCreate.js new file mode 100644 index 00000000..8922ccd7 --- /dev/null +++ b/src/queries/highlights/findOrCreate.js @@ -0,0 +1,22 @@ +import db from '../../models'; + +/** + * @param {object} data inputs data to be saved in db + * @returns {object} Object representing the response returned + */ + +export default async (highlight = {}) => { + try { + let findOrCreate = []; + findOrCreate = await db.Highlight.findOrCreate({ + where: highlight, + defaults: highlight, + logging: false + }); + return [findOrCreate[0].dataValues, findOrCreate[1]]; + } catch (error) { + return { + errors: error + }; + } +}; diff --git a/src/queries/highlights/getAll.js b/src/queries/highlights/getAll.js new file mode 100644 index 00000000..4c289ad6 --- /dev/null +++ b/src/queries/highlights/getAll.js @@ -0,0 +1,12 @@ +import db from '../../models'; +import { dbFindAll } from '../../helpers/queryHelper'; + +const getAll = async (condition = {}) => dbFindAll(db.Highlight, condition, null, null, [ + { + model: db.User, + as: 'commentAuthor', + attributes: ['firstName', 'lastName', 'username', 'email', 'image'] + } +]); + +export default getAll; diff --git a/src/queries/highlights/index.js b/src/queries/highlights/index.js new file mode 100644 index 00000000..45e4351f --- /dev/null +++ b/src/queries/highlights/index.js @@ -0,0 +1,5 @@ +import findOrCreate from './findOrCreate'; +import deleteHighlight from './deleteHighlight'; +import getAll from './getAll'; + +export { findOrCreate, getAll, deleteHighlight }; diff --git a/src/queries/index.js b/src/queries/index.js new file mode 100644 index 00000000..ee412014 --- /dev/null +++ b/src/queries/index.js @@ -0,0 +1,17 @@ +import * as User from './users'; +import * as Article from './articles'; +import * as Token from './tokens'; +import * as Tag from './tags'; +import * as Rating from './ratings'; +import * as Notification from './notifications'; +import * as Chat from './chats'; +import * as Gallery from './gallery'; +import { getAllRatings, createRatings } from './readingStats'; + +export { + User, Token, Chat, Notification, Gallery, Rating +}; + +export { + Tag, getAllRatings, Article, createRatings +}; diff --git a/src/queries/notifications/createNotification.js b/src/queries/notifications/createNotification.js new file mode 100644 index 00000000..e259b703 --- /dev/null +++ b/src/queries/notifications/createNotification.js @@ -0,0 +1,26 @@ +import db from '../../models'; + +/** + * @param {int} userId + * @param {string} message + * @param {string} preference + * @param {string} url + * @returns {object} returns the created notification + */ +export default async (userId, message, preference = 'inApp', url) => { + try { + return (await db.Notification.create( + { + userId, + message, + preference, + url + }, + { logging: false } + )).dataValues; + } catch (error) { + return { + errors: error + }; + } +}; diff --git a/src/queries/notifications/getAllNotifications.js b/src/queries/notifications/getAllNotifications.js new file mode 100644 index 00000000..c6d3b16e --- /dev/null +++ b/src/queries/notifications/getAllNotifications.js @@ -0,0 +1,30 @@ +import db from '../../models'; + +/** + * @param {int} userId + * @param {string} status + * @param {int} offset + * @param {int} limit + * @param {string} preference + * @returns {array} an array containing the list of notifications + */ +export default async (userId, status, offset = 0, limit = 1, preference = 'inApp') => { + try { + const notifications = await db.Notification.findAll({ + limit, + offset, + order: [['id', 'DESC']], + where: status ? { userId, status, preference } : { userId, preference }, + logging: false, + attributes: { + exclude: ['preference'] + } + }); + + return notifications || []; + } catch (error) { + return { + errors: error + }; + } +}; diff --git a/src/queries/notifications/getOneNotification.js b/src/queries/notifications/getOneNotification.js new file mode 100644 index 00000000..57d521c7 --- /dev/null +++ b/src/queries/notifications/getOneNotification.js @@ -0,0 +1,25 @@ +import db from '../../models'; + +/** + * @param {int} userId + * @param {int} notificationId + * @param {string} preference + * @returns {array} an array containing the list of all configurations + */ +export default async (userId, notificationId, preference = 'inApp') => { + try { + const notification = await db.Notification.findOne({ + where: { userId, id: notificationId, preference }, + logging: false, + attributes: { + exclude: ['preference'] + } + }); + + return notification ? notification.dataValues : {}; + } catch (error) { + return { + errors: error + }; + } +}; diff --git a/src/queries/notifications/index.js b/src/queries/notifications/index.js new file mode 100644 index 00000000..eede3dcf --- /dev/null +++ b/src/queries/notifications/index.js @@ -0,0 +1,10 @@ +import create from './createNotification'; +import getAll from './getAllNotifications'; +import getOne from './getOneNotification'; +import remove from './removeNotification'; +import update from './updateNotifications'; +import * as config from '../notificationsConfig'; + +export { + create, getAll, getOne, config, remove, update +}; diff --git a/src/queries/notifications/removeNotification.js b/src/queries/notifications/removeNotification.js new file mode 100644 index 00000000..daa409f9 --- /dev/null +++ b/src/queries/notifications/removeNotification.js @@ -0,0 +1,20 @@ +import db from '../../models'; + +/** + * @param {int} notificationId the id of the user + * @param {int} userId the id of the user + * @param {string} preference + * @returns {int} return the number of affected rows + */ +export default async (notificationId, userId, preference = 'inApp') => { + try { + return await db.Notification.destroy({ + where: { id: notificationId, userId, preference }, + logging: false + }); + } catch (error) { + return { + errors: error + }; + } +}; diff --git a/src/queries/notifications/updateNotifications.js b/src/queries/notifications/updateNotifications.js new file mode 100644 index 00000000..02f4b1ef --- /dev/null +++ b/src/queries/notifications/updateNotifications.js @@ -0,0 +1,33 @@ +import db from '../../models'; + +/** + * @param {integer} userId + * @param {integer} notificationId + * @param {object} value + * @param {string} preference + * @returns {object} return an object containing updated configuration + */ +export default async (userId, notificationId, value, preference = 'inApp') => { + try { + const condition = notificationId ? { id: notificationId, userId } : { userId }; + Object.keys(value).forEach(key => value[key] || delete value[key]); + + const notifications = await db.Notification.update(value, { + where: value.preference ? condition : { ...condition, preference }, + returning: true, + logging: false + }); + + return ( + (notifications[0] + && notifications[1].map( + notification => delete notification.get().preference && notification.get() + )) + || [] + ); + } catch (error) { + return { + errors: error + }; + } +}; diff --git a/src/queries/notificationsConfig/createNotificationConfig.js b/src/queries/notificationsConfig/createNotificationConfig.js new file mode 100644 index 00000000..43f3e735 --- /dev/null +++ b/src/queries/notificationsConfig/createNotificationConfig.js @@ -0,0 +1,21 @@ +import db from '../../models'; + +/** + * @param {int} userId + * @param {object} config + * @returns {object} returns the created configuration + */ +export default async (userId, config) => { + try { + const createdConfig = await db.NotificationConfig.create( + { userId, config: JSON.stringify(config) }, + { logging: false } + ); + + return createdConfig.get(); + } catch (error) { + return { + errors: error + }; + } +}; diff --git a/src/queries/notificationsConfig/getOneNotificationConfig.js b/src/queries/notificationsConfig/getOneNotificationConfig.js new file mode 100644 index 00000000..379e8ca2 --- /dev/null +++ b/src/queries/notificationsConfig/getOneNotificationConfig.js @@ -0,0 +1,20 @@ +import db from '../../models'; + +/** + * @param {int} userId + * @returns {array} an array containing the list of all configurations + */ +export default async (userId) => { + try { + const config = await db.NotificationConfig.findOne({ + where: { userId }, + logging: false + }); + + return config ? config.get() : {}; + } catch (error) { + return { + errors: error + }; + } +}; diff --git a/src/queries/notificationsConfig/index.js b/src/queries/notificationsConfig/index.js new file mode 100644 index 00000000..b0b6b419 --- /dev/null +++ b/src/queries/notificationsConfig/index.js @@ -0,0 +1,5 @@ +import create from './createNotificationConfig'; +import update from './updateNotificationConfig'; +import getOne from './getOneNotificationConfig'; + +export { create, update, getOne }; diff --git a/src/queries/notificationsConfig/updateNotificationConfig.js b/src/queries/notificationsConfig/updateNotificationConfig.js new file mode 100644 index 00000000..9cab366e --- /dev/null +++ b/src/queries/notificationsConfig/updateNotificationConfig.js @@ -0,0 +1,24 @@ +import db from '../../models'; + +/** + * @param {integer} userId where to update + * @param {object} config what to update + * @returns {object} return an object containing updated configuration + */ +export default async (userId, config) => { + try { + const updatedConfig = await db.NotificationConfig.update( + { config: JSON.stringify(config) }, + { + where: { userId }, + returning: true, + logging: false + } + ); + return updatedConfig[0] ? updatedConfig[1][0].get() : {}; + } catch (error) { + return { + errors: error + }; + } +}; diff --git a/src/queries/permissions/createPermission.js b/src/queries/permissions/createPermission.js new file mode 100644 index 00000000..16ffc379 --- /dev/null +++ b/src/queries/permissions/createPermission.js @@ -0,0 +1,17 @@ +import db from '../../models'; + +/** + * @param {object} userType + * @param {object} permissions + * @returns {array} an array containing an object of permissions and a boolean + * to check if the permissions were created or not + */ +export default async (userType, permissions) => { + try { + return (await db.Permission.create({ userType, permissions }, { logging: false })).get(); + } catch (error) { + return { + errors: error + }; + } +}; diff --git a/src/queries/permissions/findAllPermission.js b/src/queries/permissions/findAllPermission.js new file mode 100644 index 00000000..81d32787 --- /dev/null +++ b/src/queries/permissions/findAllPermission.js @@ -0,0 +1,11 @@ +import db from '../../models'; +import { dbFindAll } from '../../helpers/queryHelper'; + +/** + * @param {object} condition + * @returns {object} an object the return permissions + */ + +const getAll = async (condition = {}) => dbFindAll(db.Permission, condition); + +export default getAll; diff --git a/src/queries/permissions/findOrCreatePermission.js b/src/queries/permissions/findOrCreatePermission.js new file mode 100644 index 00000000..c5ab79fc --- /dev/null +++ b/src/queries/permissions/findOrCreatePermission.js @@ -0,0 +1,22 @@ +import db from '../../models'; + +/** + * @param {object} userType + * @param {object} permissions + * @returns {array} an array containing an object of permissions and a boolean + * to check if the permissions were created or not + */ +export default async (userType, permissions) => { + try { + const findOrCreate = await db.Permission.findOrCreate({ + where: { userType }, + defaults: { userType, permissions }, + logging: false + }); + return [findOrCreate[0].get(), findOrCreate[1]]; + } catch (error) { + return { + errors: error + }; + } +}; diff --git a/src/queries/permissions/index.js b/src/queries/permissions/index.js new file mode 100644 index 00000000..7b2999b4 --- /dev/null +++ b/src/queries/permissions/index.js @@ -0,0 +1,4 @@ +import create from './createPermission'; +import findAll from './findAllPermission'; + +export { create, findAll }; diff --git a/src/queries/ratings/createOrUpdate.js b/src/queries/ratings/createOrUpdate.js new file mode 100644 index 00000000..b29df906 --- /dev/null +++ b/src/queries/ratings/createOrUpdate.js @@ -0,0 +1,32 @@ +import db from '../../models'; +import findAll from './findAll'; +import update from './update'; + +/** + * @param {object} rating input user rating + * @returns {object} Object representing the response returned + */ + +export default async (data = {}) => { + const { articleId, userId, rating } = data; + try { + const result = await findAll(articleId, userId); + let message = {}; + if (result && result.length === 0) { + await db.Rating.create({ rating, articleId, userId }, { logging: false }); + message = 'created'; + } else { + await db.Rating.update( + { rating }, + { where: { [db.Op.and]: { articleId, userId } }, logging: false } + ); + message = 'updated'; + } + await update(articleId); + return message; + } catch (err) { + return { + errors: err + }; + } +}; diff --git a/src/queries/ratings/findAll.js b/src/queries/ratings/findAll.js new file mode 100644 index 00000000..cf492985 --- /dev/null +++ b/src/queries/ratings/findAll.js @@ -0,0 +1,14 @@ +import db from '../../models'; + +export default async (articleId, userId) => { + const allRating = await db.Rating.findAll({ + limit: 1, + where: { + articleId, + userId + }, + attributes: ['id', 'articleId', 'userId', 'rating'], + logging: false + }); + return allRating; +}; diff --git a/src/queries/ratings/findAllByArticle.js b/src/queries/ratings/findAllByArticle.js new file mode 100644 index 00000000..32ebb94a --- /dev/null +++ b/src/queries/ratings/findAllByArticle.js @@ -0,0 +1,12 @@ +import db from '../../models'; + +export default async (articleId) => { + const allRating = await db.Rating.findAll({ + where: { + articleId + }, + attributes: ['id', 'articleId', 'userId', 'rating'], + logging: false + }); + return allRating; +}; diff --git a/src/queries/ratings/get.js b/src/queries/ratings/get.js new file mode 100644 index 00000000..230f4fc8 --- /dev/null +++ b/src/queries/ratings/get.js @@ -0,0 +1,19 @@ +import db from '../../models'; + +/** + * Get article rating + * @param {object} where condition for query + * @returns {object} Object representing the response returned + */ +export default async (where = {}) => db.Rating.findAll({ + where, + logging: false, + attributes: ['id', 'rating', 'createdAt'], + include: [ + { + model: db.User, + as: 'author', + attributes: ['username', 'firstName', 'lastName', 'bio', 'image', 'createdAt'] + } + ] +}); diff --git a/src/queries/ratings/getAll.js b/src/queries/ratings/getAll.js new file mode 100644 index 00000000..8ca5ef80 --- /dev/null +++ b/src/queries/ratings/getAll.js @@ -0,0 +1,21 @@ +import db from '../../models'; + +export default async (limit, offset, articleId) => { + const allRatings = await db.Rating.findAll({ + limit, + offset, + where: { + articleId + }, + logging: false, + attributes: ['id', 'rating', 'createdAt'], + include: [ + { + model: db.User, + as: 'author', + attributes: ['username', 'firstName', 'lastName', 'bio', 'image', 'createdAt'] + } + ] + }); + return allRatings; +}; diff --git a/src/queries/ratings/index.js b/src/queries/ratings/index.js new file mode 100644 index 00000000..96d47f54 --- /dev/null +++ b/src/queries/ratings/index.js @@ -0,0 +1,11 @@ +import create from './createOrUpdate'; +import update from './update'; +import get from './get'; +import findAll from './findAll'; +import findAllByArticle from './findAllByArticle'; +import sort from './sortArticlesByRatings'; +import articleRatings from './getAll'; + +export { + create, update, get, findAll, findAllByArticle, sort, articleRatings +}; diff --git a/src/queries/ratings/sortArticlesByRatings.js b/src/queries/ratings/sortArticlesByRatings.js new file mode 100644 index 00000000..f2b0f09d --- /dev/null +++ b/src/queries/ratings/sortArticlesByRatings.js @@ -0,0 +1,29 @@ +import db from '../../models'; +import * as filters from '../../helpers/searchArticleFilters'; + +/** + * Get specific article ratings + * @param {object} limit limit for query + * @param {object} offset offset for query + * @param {object} condition contains author, tag and keyword to filter query + * @returns {object} Object representing the response returned + */ +export default async (limit, offset, { keyword, author, tag }) => { + const where = filters.filterQueryBuilder({ keyword, author, tag }); + const paginate = { limit, offset }; + let response = []; + response = await db.Article.findAll({ + paginate, + where, + order: [['rating', 'DESC']], + logging: false, + include: [ + { + model: db.User, + as: 'author', + attributes: ['username', 'bio', 'image'] + } + ] + }); + return response; +}; diff --git a/src/queries/ratings/update.js b/src/queries/ratings/update.js new file mode 100644 index 00000000..d3db3792 --- /dev/null +++ b/src/queries/ratings/update.js @@ -0,0 +1,13 @@ +import db from '../../models'; + +import findAllByArticle from './findAllByArticle'; + +export default async (articleId) => { + const allRatings = await findAllByArticle(articleId); + let total = 0; + allRatings.forEach((value) => { + total += value.dataValues.rating; // calculate current rating + }); + const rating = parseFloat((total / allRatings.length).toFixed(1), 10); + await db.Article.update({ rating }, { where: { id: articleId }, logging: false }); +}; diff --git a/src/queries/readingStats/create.js b/src/queries/readingStats/create.js new file mode 100644 index 00000000..8032d9ec --- /dev/null +++ b/src/queries/readingStats/create.js @@ -0,0 +1,15 @@ +import db from '../../models'; + +/** + * @param {object} data inputs data to be saved in db + * @returns {object} Object representing the response returned + */ +export default async (data) => { + let stats = []; + stats = await db.ReadingStat.findOrCreate({ + where: data, + defaults: 1, + logging: false + }); + return stats; +}; diff --git a/src/queries/readingStats/getAll.js b/src/queries/readingStats/getAll.js new file mode 100644 index 00000000..80b931e9 --- /dev/null +++ b/src/queries/readingStats/getAll.js @@ -0,0 +1,26 @@ +import db from '../../models'; + +/** + * Get user stats + * @param {object} condition condition for query + * @returns {object} Object representing the response returned + */ +export default async (condition = {}) => db.ReadingStat.findAndCountAll({ + where: condition, + logging: false, + include: [ + { + model: db.User, + as: 'user', + attributes: { + exclude: ['createdAt', 'updatedAt'] + } + }, + { + model: db.Article, + as: 'article', + attributes: ['id', 'title', 'description', 'coverUrl', 'readTime'] + } + ], + order: [['createdAt', 'DESC']] +}); diff --git a/src/queries/readingStats/index.js b/src/queries/readingStats/index.js new file mode 100644 index 00000000..96ddc92a --- /dev/null +++ b/src/queries/readingStats/index.js @@ -0,0 +1,4 @@ +import getAllRatings from './getAll'; +import createRatings from './create'; + +export { getAllRatings, createRatings }; diff --git a/src/queries/reports/create.js b/src/queries/reports/create.js new file mode 100644 index 00000000..b3ffd705 --- /dev/null +++ b/src/queries/reports/create.js @@ -0,0 +1,6 @@ +import db from '../../models'; +import { dbCreate } from '../../helpers/queryHelper'; + +const create = async data => dbCreate(db.Report, data); + +export default create; diff --git a/src/queries/reports/getAll.js b/src/queries/reports/getAll.js new file mode 100644 index 00000000..ae9e1710 --- /dev/null +++ b/src/queries/reports/getAll.js @@ -0,0 +1,11 @@ +import db from '../../models'; +import { dbFindAll } from '../../helpers/queryHelper'; + +const getAll = async condition => dbFindAll(db.Report, condition, null, null, [ + { + model: db.User, + as: 'reporter', + attributes: ['firstName', 'lastName', 'username', 'email', 'image'] + } +]); +export default getAll; diff --git a/src/queries/reports/getSingle.js b/src/queries/reports/getSingle.js new file mode 100644 index 00000000..e1a486d5 --- /dev/null +++ b/src/queries/reports/getSingle.js @@ -0,0 +1,5 @@ +import db from '../../models'; +import { dbFindSingle } from '../../helpers/queryHelper'; + +const getSingle = async (condition = {}) => dbFindSingle(db.Report, condition); +export default getSingle; diff --git a/src/queries/reports/index.js b/src/queries/reports/index.js new file mode 100644 index 00000000..b021557f --- /dev/null +++ b/src/queries/reports/index.js @@ -0,0 +1,8 @@ +import create from './create'; +import remove from './remove'; +import getAll from './getAll'; +import getSingle from './getSingle'; + +export { + create, remove, getAll, getSingle +}; diff --git a/src/queries/reports/remove.js b/src/queries/reports/remove.js new file mode 100644 index 00000000..1cb95ccf --- /dev/null +++ b/src/queries/reports/remove.js @@ -0,0 +1,5 @@ +import db from '../../models'; +import { dbDelete } from '../../helpers/queryHelper'; + +const remove = async (condition = {}) => dbDelete(db.Report, condition); +export default remove; diff --git a/src/queries/tags/get.js b/src/queries/tags/get.js new file mode 100644 index 00000000..4c8cf030 --- /dev/null +++ b/src/queries/tags/get.js @@ -0,0 +1,20 @@ +import db from '../../models'; + +/** + * Get specific article + * @returns {object} Array contains a list of tags in all articles + */ +export default async () => { + const response = await db.Article.findAll({ + attributes: ['tagList'], + limit: 10 + }); + let listOfTags = []; + await Object.values(response).forEach((value) => { + Object.values(value.dataValues.tagList).forEach((val) => { + listOfTags = [...listOfTags, val]; + }); + }); + const treatedTags = [...new Set(listOfTags)].sort(); + return treatedTags; +}; diff --git a/src/queries/tags/index.js b/src/queries/tags/index.js new file mode 100644 index 00000000..a235880d --- /dev/null +++ b/src/queries/tags/index.js @@ -0,0 +1,4 @@ +import update from './update'; +import get from './get'; + +export { update, get }; diff --git a/src/queries/tags/update.js b/src/queries/tags/update.js new file mode 100644 index 00000000..e37e9c39 --- /dev/null +++ b/src/queries/tags/update.js @@ -0,0 +1,40 @@ +import db from '../../models'; + +/** + * @param {object} newTags tag lists data to be saved in db + * @param {object} slug article identify + * @param {object} action action method to perform¬ + * @returns {object} Array of updated tag list of the article + */ +export default async (newTags, slug, action) => { + let result = []; + let message = ''; + const response = await db.Article.findAll({ + limit: 1, + where: { slug }, + attributes: ['tagList'], + logging: false + }); + if (action === 'update') { + result = response[0].dataValues.tagList + ? [...new Set([...response[0].dataValues.tagList, ...newTags])] + : newTags; + message = 'Tag list has been updated'; + } else { + result = response[0].dataValues.tagList.filter(item => !newTags.includes(item)); + message = 'Tag has been deleted'; + } + + if (result.length <= 5) { + await db.Article.update( + { tagList: result }, + { + where: { slug }, + logging: false + } + ); + } else { + message = 'You can not create more than 5 tags per article'; + } + return message; +}; diff --git a/src/queries/tokens/destroyToken.js b/src/queries/tokens/destroyToken.js new file mode 100644 index 00000000..57602195 --- /dev/null +++ b/src/queries/tokens/destroyToken.js @@ -0,0 +1,23 @@ +import db from '../../models'; + +/** + * @param {int} userId + * @returns {int} return the number of affected rows + */ +export default async (userId) => { + try { + const { Op } = db.Sequelize; + const deletedToken = await db.Token.destroy({ + where: { + userId, + createdAt: { [Op.lte]: new Date(new Date() - 86400000) } + }, + logging: false + }); + return deletedToken; + } catch (error) { + return { + errors: error + }; + } +}; diff --git a/src/queries/tokens/findOneToken.js b/src/queries/tokens/findOneToken.js new file mode 100644 index 00000000..0ec4722b --- /dev/null +++ b/src/queries/tokens/findOneToken.js @@ -0,0 +1,23 @@ +import db from '../../models'; + +/** + * @param {int} userId + * @param {string} token + * @returns {object} an object containing the token and the user ID or null + */ +export default async (userId, token) => { + try { + const findToken = userId + ? await db.Token.findOne({ + where: { userId, token }, + logging: false + }) + : null; + + return findToken ? findToken.dataValues : {}; + } catch (error) { + return { + errors: error + }; + } +}; diff --git a/src/queries/tokens/index.js b/src/queries/tokens/index.js new file mode 100644 index 00000000..63a18e97 --- /dev/null +++ b/src/queries/tokens/index.js @@ -0,0 +1,5 @@ +import save from './saveToken'; +import findOne from './findOneToken'; +import destroy from './destroyToken'; + +export { save, findOne, destroy }; diff --git a/src/queries/tokens/saveToken.js b/src/queries/tokens/saveToken.js new file mode 100644 index 00000000..ebaf0506 --- /dev/null +++ b/src/queries/tokens/saveToken.js @@ -0,0 +1,18 @@ +import db from '../../models'; + +/** + * @param {string} token + * @param {int} userId + * @returns {object} return the saved token + */ +export default async (token = '', userId = 0) => { + try { + const newToken = await db.Token.create({ token, userId }, { logging: false }); + + return newToken.dataValues; + } catch (error) { + return { + errors: error + }; + } +}; diff --git a/src/queries/tokens/updateToken.js b/src/queries/tokens/updateToken.js new file mode 100644 index 00000000..60db0c38 --- /dev/null +++ b/src/queries/tokens/updateToken.js @@ -0,0 +1,20 @@ +import db from '../../models'; + +/** + * @param {string} token the new token + * @param {int} userId the id of the user + * @returns {object} return the updated token when successfuly updated + */ +export default async (token = '', userId = 0) => { + try { + const updatedToken = await db.Token.update( + { token }, + { where: { userId }, logging: false, returning: true } + ); + return updatedToken[0] ? updatedToken[1][0].dataValues : {}; + } catch (error) { + return { + errors: error.message + }; + } +}; diff --git a/src/queries/users/createUser.js b/src/queries/users/createUser.js new file mode 100644 index 00000000..48fd32cc --- /dev/null +++ b/src/queries/users/createUser.js @@ -0,0 +1,17 @@ +import db from '../../models'; + +/** + * @param {object} user + * @returns {object} an object containing the information of the user or null + */ +export default async (user = {}) => { + try { + const newUser = await db.User.create(user, { logging: false }); + + return newUser.dataValues; + } catch (error) { + return { + errors: error + }; + } +}; diff --git a/src/queries/users/findOneUser.js b/src/queries/users/findOneUser.js new file mode 100644 index 00000000..26783d64 --- /dev/null +++ b/src/queries/users/findOneUser.js @@ -0,0 +1,22 @@ +import db from '../../models'; + +/** + * @param {object} condition + * @returns {object} an object containing the information of the user or null + */ +export default async (condition = {}) => { + try { + const user = Object.keys(condition).length + ? await db.User.findOne({ + where: condition, + logging: false + }) + : null; + + return user ? user.dataValues : {}; + } catch (error) { + return { + errors: error + }; + } +}; diff --git a/src/queries/users/findOrCreateUser.js b/src/queries/users/findOrCreateUser.js new file mode 100644 index 00000000..2eacdc3e --- /dev/null +++ b/src/queries/users/findOrCreateUser.js @@ -0,0 +1,22 @@ +import db from '../../models'; + +/** + * @param {object} condition + * @param {object} user + * @returns {array} an array containing the user object and a boolean + * to check if the user was created or not + */ +export default async (condition = {}, user = {}) => { + try { + const findOrCreate = await db.User.findOrCreate({ + where: condition, + defaults: user, + logging: false + }); + return [findOrCreate[0].get(), findOrCreate[1]]; + } catch (error) { + return { + errors: error + }; + } +}; diff --git a/src/queries/users/getAllUser.js b/src/queries/users/getAllUser.js new file mode 100644 index 00000000..b513ed35 --- /dev/null +++ b/src/queries/users/getAllUser.js @@ -0,0 +1,4 @@ +import db from '../../models'; +import { dbFindAll } from '../../helpers/queryHelper'; + +export default async (condition, offset, limit) => dbFindAll(db.User, condition, offset, limit); diff --git a/src/queries/users/index.js b/src/queries/users/index.js new file mode 100644 index 00000000..2f857f2d --- /dev/null +++ b/src/queries/users/index.js @@ -0,0 +1,14 @@ +import create from './createUser'; +import findOne from './findOneUser'; +import update from './updateUser'; +import findOrCreate from './findOrCreateUser'; +import * as permissions from '../permissions'; +import getAllUser from './getAllUser'; +import searchUsersByUsername from './searchUsersByUsername'; +import * as follow from '../follows'; + +export { + create, findOne, update, findOrCreate +}; +export { getAllUser, permissions, follow }; +export { searchUsersByUsername }; diff --git a/src/queries/users/searchUsersByUsername.js b/src/queries/users/searchUsersByUsername.js new file mode 100644 index 00000000..4f2eab1f --- /dev/null +++ b/src/queries/users/searchUsersByUsername.js @@ -0,0 +1,17 @@ +import db from '../../models'; +import { dbFindAll } from '../../helpers/queryHelper'; + +export default async (username, offset = 0, limit = 50) => dbFindAll( + db.User, + { + [db.Op.and]: [ + { + username: { + [db.Op.iLike]: `${username}%` + } + } + ] + }, + offset, + limit +); diff --git a/src/queries/users/updateUser.js b/src/queries/users/updateUser.js new file mode 100644 index 00000000..7af2f076 --- /dev/null +++ b/src/queries/users/updateUser.js @@ -0,0 +1,22 @@ +import db from '../../models'; + +/** + * @param {object} value what to update + * @param {object} condition where to update + * @returns {object} return an object containing updated user information + * when successfully updated otherwise false + */ +export default async (value = {}, condition = {}) => { + try { + const updatedUser = await db.User.update(value, { + where: condition, + returning: true, + logging: false + }); + return updatedUser[0] ? updatedUser[1][0].get() : {}; + } catch (error) { + return { + errors: error + }; + } +}; diff --git a/src/routes/api/articles.js b/src/routes/api/articles.js new file mode 100644 index 00000000..c296f053 --- /dev/null +++ b/src/routes/api/articles.js @@ -0,0 +1,183 @@ +import { Router } from 'express'; +import ArticleController from '../../controllers/ArticleController'; +import validateArticle from '../../middlewares/validation/articles'; +import asyncHandler from '../../middlewares/asyncHandler'; +import checkArticleBySlug from '../../middlewares/checkArticleBySlug'; +import verifyToken from '../../middlewares/verifyToken'; +import shareArticle from '../../middlewares/shareArticle'; +import multerUploads from '../../middlewares/multerUploads'; +import checkPermissions from '../../middlewares/checkPermissions'; +import checkArticlePermissions from '../../middlewares/checkArticlePermissions'; + +const articles = Router(); + +// create article +articles.post( + '/articles', + verifyToken, + checkPermissions({ + route: 'articles', + action: 'create' + }), + multerUploads.array('coverUrl', 1), + validateArticle.create, + asyncHandler(ArticleController.saveArticle) +); + +articles.get('/articles/bookmarked', verifyToken, ArticleController.getBookmarksOrFavorites); +articles.get('/articles/favorited', verifyToken, ArticleController.getBookmarksOrFavorites); + +articles.get( + '/articles', + validateArticle.pagination, + asyncHandler(ArticleController.getAllArticles) +); +articles.get( + '/articles/drafts', + verifyToken, + checkPermissions({ + route: 'articles', + action: 'read' + }), + asyncHandler(ArticleController.userArticleDrafts) +); +articles.get( + '/articles/published', + verifyToken, + checkPermissions({ + route: 'articles', + action: 'read' + }), + asyncHandler(ArticleController.userArticlePublished) +); +articles.get( + '/articles/:slug', + checkArticleBySlug, + (req, res, next) => (req.article.status !== 'published' + && res.status(404).json({ + errors: { + article: 'article not found' + } + })) + || next(), + asyncHandler(ArticleController.getSpecificArticle) +); +articles.get( + '/profile/articles/:slug', + verifyToken, + checkArticleBySlug, + checkPermissions({ route: 'articles', action: 'read' }), + checkArticlePermissions({ + normal: 'self', + admin: 'self' + }), + asyncHandler(ArticleController.getSpecificArticle) +); +articles.put( + '/articles/:slug', + validateArticle.update, + verifyToken, + checkArticleBySlug, + checkPermissions({ route: 'articles', action: 'edit' }), + checkArticlePermissions({ + normal: 'self', + admin: 'self' + }), + asyncHandler(ArticleController.update) +); + +articles.put( + '/articles/:slug/publish', + verifyToken, + validateArticle.slug, + checkArticleBySlug, + checkPermissions({ route: 'articles', action: 'edit' }), + checkArticlePermissions({ normal: 'self', admin: 'self' }), + asyncHandler(ArticleController.update) +); + +articles.put( + '/articles/:slug/unpublish', + verifyToken, + checkArticleBySlug, + validateArticle.slug, + checkPermissions({ route: 'articles', action: 'edit' }), + checkArticlePermissions({ + normal: 'self', + admin: 'all' + }), + asyncHandler(ArticleController.update) +); + +articles.delete( + '/articles/:slug', + verifyToken, + checkPermissions({ route: 'articles', action: 'delete' }), + checkArticleBySlug, + checkArticlePermissions({ + normal: 'self', + admin: 'all' + }), + asyncHandler(ArticleController.update) +); + +articles.patch( + '/articles/:slug/bookmark', + verifyToken, + checkArticleBySlug, + ArticleController.bookmarkOrFavorite +); +articles.patch( + '/articles/:slug/favorite', + verifyToken, + checkArticleBySlug, + ArticleController.bookmarkOrFavorite +); + +articles.delete( + '/articles/:slug/bookmark', + verifyToken, + checkArticleBySlug, + ArticleController.removeBookmarkOrFavorite +); + +articles.delete( + '/articles/:slug/favorite', + verifyToken, + checkArticleBySlug, + ArticleController.removeBookmarkOrFavorite +); + +articles.get( + '/articles/:slug/share/facebook', + verifyToken, + checkArticleBySlug, + shareArticle, + ArticleController.share +); + +articles.get( + '/articles/:slug/share/twitter', + verifyToken, + checkArticleBySlug, + shareArticle, + ArticleController.share +); + +articles.get( + '/articles/:slug/share/linkedin', + verifyToken, + checkArticleBySlug, + shareArticle, + ArticleController.share +); + +articles.get( + '/articles/:slug/share/gmail', + verifyToken, + checkArticleBySlug, + shareArticle, + ArticleController.share +); + +export default articles; diff --git a/src/routes/api/chats.js b/src/routes/api/chats.js new file mode 100644 index 00000000..2be11fde --- /dev/null +++ b/src/routes/api/chats.js @@ -0,0 +1,13 @@ +import express from 'express'; +import verifyToken from '../../middlewares/verifyToken'; +import ChatController from '../../controllers/ChatController'; + +const router = express.Router(); + +router.post('/', verifyToken, ChatController.save); + +router.get('/', verifyToken, ChatController.getAll); + +router.delete('/:chatId', verifyToken, ChatController.delete); + +export default router; diff --git a/src/routes/api/comments.js b/src/routes/api/comments.js new file mode 100644 index 00000000..4ea4d24a --- /dev/null +++ b/src/routes/api/comments.js @@ -0,0 +1,81 @@ +import { Router } from 'express'; +import CommentController from '../../controllers/CommentController'; +import Validation from '../../middlewares/validateComment'; +import checkArticle from '../../middlewares/checkArticle'; +import checkComment from '../../middlewares/checkComment'; +import asyncHandler from '../../middlewares/asyncHandler'; +import verifyToken from '../../middlewares/verifyToken'; +import checkPermissions from '../../middlewares/checkPermissions'; + +const router = Router(); + +router.post( + '/:articleSlug/comments', + verifyToken, + checkPermissions({ + route: 'comments', + action: 'create' + }), + Validation, + checkArticle, + asyncHandler(CommentController.create) +); +router.get('/:articleSlug/comments', checkArticle, asyncHandler(CommentController.getAll)); +router.patch( + '/:articleSlug/comments/:commentId', + verifyToken, + checkPermissions({ + route: 'comments', + action: 'edit' + }), + Validation, + checkArticle, + checkComment, + asyncHandler(CommentController.editComment) +); +router.delete( + '/:articleSlug/comments/:commentId', + verifyToken, + checkPermissions({ + route: 'comments', + action: 'delete' + }), + checkArticle, + checkComment, + asyncHandler(CommentController.delete) +); +router.get( + '/:articleSlug/comments/:commentId/edits', + verifyToken, + checkPermissions({ + route: 'comments', + action: 'read' + }), + checkArticle, + checkComment, + asyncHandler(CommentController.getAllEdit) +); +router.delete( + '/:articleSlug/comments/:commentId', + verifyToken, + checkPermissions({ + route: 'comments', + action: 'delete' + }), + checkArticle, + checkComment, + asyncHandler(CommentController.delete) +); +router.delete( + '/:articleSlug/comments/:commentId/edits/:id', + verifyToken, + checkPermissions({ + route: 'comments', + action: 'delete' + }), + checkArticle, + checkComment, + asyncHandler(CommentController.remove) +); + +export default router; diff --git a/src/routes/api/highlights.js b/src/routes/api/highlights.js new file mode 100644 index 00000000..987aff26 --- /dev/null +++ b/src/routes/api/highlights.js @@ -0,0 +1,31 @@ +import { Router } from 'express'; +import HighlightController from '../../controllers/HighlightController'; +import validateHighlight from '../../middlewares/validation/highlights'; +import checkArticle from '../../middlewares/checkArticle'; +import verifyToken from '../../middlewares/verifyToken'; +import asyncHandler from '../../middlewares/asyncHandler'; + +const router = Router(); + +router.post( + '/:articleSlug/highlights', + verifyToken, + validateHighlight.create, + checkArticle, + asyncHandler(HighlightController.create) +); + +router.get( + '/:articleSlug/highlights', + checkArticle, + asyncHandler(HighlightController.getHighlights) +); + +router.delete( + '/:articleSlug/highlights/:id', + verifyToken, + checkArticle, + asyncHandler(HighlightController.deleteHighlights) +); + +export default router; diff --git a/src/routes/api/index.js b/src/routes/api/index.js new file mode 100644 index 00000000..6b0b1e8b --- /dev/null +++ b/src/routes/api/index.js @@ -0,0 +1,34 @@ +import express from 'express'; +import articles from './articles'; +import comments from './comments'; +import tags from './tags'; +import users from './users'; +import chats from './chats'; +import upload from './upload'; +import likes from './likes'; +import report from './reports'; +import roles from './roles'; +import permissions from './permissions'; +import highlights from './highlights'; +import notifications from './notifications'; +import rating from './rating'; +import readingStats from './readingStats'; + +const router = express.Router(); + +router.use(articles); +router.use('/users/roles', roles); +router.use(tags); +router.use(rating); +router.use('/articles', comments); +router.use('/users', users); +router.use('/chats', chats); +router.use(upload); +router.use('/articles', likes); +router.use('/article', report); +router.use('/permissions', permissions); +router.use(highlights); +router.use('/notifications', notifications); +router.use('/user', readingStats); + +export default router; diff --git a/src/routes/api/likes.js b/src/routes/api/likes.js new file mode 100644 index 00000000..f5d88230 --- /dev/null +++ b/src/routes/api/likes.js @@ -0,0 +1,35 @@ +import { Router } from 'express'; +import CommentLikeController from '../../controllers/CommentLikeController'; +import checkArticle from '../../middlewares/checkArticle'; +import verifyToken from '../../middlewares/verifyToken'; +import ArticleLikeController from '../../controllers/ArticleLikeController'; +import checkComment from '../../middlewares/checkComment'; +import asyncHandler from '../../middlewares/asyncHandler'; +import checkArticleLike from '../../middlewares/checkArticleLike'; +import checkCommentLike from '../../middlewares/checkCommentLike'; + +const router = Router(); +router.get('/:articleSlug/Likes', checkArticle, asyncHandler(ArticleLikeController.getAllLikes)); +router.post( + '/:articleSlug/comments/:commentId/like', + verifyToken, + checkArticle, + checkComment, + checkCommentLike, + asyncHandler(CommentLikeController.create) +); +router.get( + '/:articleSlug/comments/:commentId/likes', + checkArticle, + checkComment, + asyncHandler(CommentLikeController.getAll) +); +router.post( + '/:articleSlug/:status', + verifyToken, + checkArticle, + checkArticleLike, + asyncHandler(ArticleLikeController.createLike) +); + +export default router; diff --git a/src/routes/api/notifications.js b/src/routes/api/notifications.js new file mode 100644 index 00000000..97f2e3f7 --- /dev/null +++ b/src/routes/api/notifications.js @@ -0,0 +1,43 @@ +import { Router } from 'express'; +import NotificationController from '../../controllers/NotificationController'; +import verifyToken from '../../middlewares/verifyToken'; +import validateNotificationConfig from '../../middlewares/validateNotificationConfig'; +import checkUpdateNotification from '../../middlewares/checkUpdateNotification'; + +const router = Router(); + +router.get('/configuration', verifyToken, NotificationController.getConfig); +router.post( + '/configuration', + verifyToken, + validateNotificationConfig, + NotificationController.setConfig +); +router.put( + '/configuration', + verifyToken, + validateNotificationConfig, + NotificationController.updateConfig +); +router.get('/', verifyToken, NotificationController.getAll); +router.get('/seen', verifyToken, NotificationController.getAll); +router.get('/unseen', verifyToken, NotificationController.getAll); +router.get('/:notificationId', verifyToken, NotificationController.getOne); +router.put('/seen', verifyToken, checkUpdateNotification, NotificationController.update); +router.put('/unseen', verifyToken, checkUpdateNotification, NotificationController.update); +router.put('/:notificationId', verifyToken, checkUpdateNotification, NotificationController.update); +router.put( + '/:notificationId/seen', + verifyToken, + checkUpdateNotification, + NotificationController.update +); +router.put( + '/:notificationId/unseen', + verifyToken, + checkUpdateNotification, + NotificationController.update +); +router.delete('/:notificationId', verifyToken, NotificationController.delete); + +export default router; diff --git a/src/routes/api/permissions.js b/src/routes/api/permissions.js new file mode 100644 index 00000000..aab3485f --- /dev/null +++ b/src/routes/api/permissions.js @@ -0,0 +1,26 @@ +import { Router } from 'express'; +import verifyToken from '../../middlewares/verifyToken'; +import PermissionController from '../../controllers/PermissionController'; +import validatePermissions from '../../middlewares/validation/permissions'; +import checkPermissions from '../../middlewares/checkPermissions'; +import asyncHandler from '../../middlewares/asyncHandler'; +import verifyAdmin from '../../middlewares/verifyAdmin'; + +const router = Router(); + +router.post( + '/', + verifyToken, + validatePermissions, + checkPermissions({ + route: 'users', + action: 'create', + message: 'you can not create this permission' + }), + asyncHandler(PermissionController.create) +); + +router.get('/', verifyToken, verifyAdmin, asyncHandler(PermissionController.findAll)); +router.get('/:userType', verifyToken, verifyAdmin, asyncHandler(PermissionController.findAll)); + +export default router; diff --git a/src/routes/api/rating.js b/src/routes/api/rating.js new file mode 100644 index 00000000..79f16c4e --- /dev/null +++ b/src/routes/api/rating.js @@ -0,0 +1,37 @@ +import { Router } from 'express'; +import RatingController from '../../controllers/RatingController'; +import asyncHandler from '../../middlewares/asyncHandler'; +import checkArticleBySlug from '../../middlewares/checkArticleBySlug'; +import validateArticle from '../../middlewares/validation/articles'; +import validateRating from '../../middlewares/validation/validateRating'; +import verifyToken from '../../middlewares/verifyToken'; +import checkPermissions from '../../middlewares/checkPermissions'; + +const rating = Router(); + +rating.post( + '/rating/:slug/article', + verifyToken, + checkArticleBySlug, + validateRating.create, + checkPermissions({ + route: 'articles', + action: 'create' + }), + asyncHandler(RatingController.create) +); + +rating.get('/rating/:slug/article', checkArticleBySlug, asyncHandler(RatingController.get)); +rating.get( + '/rating/articles', + validateArticle.pagination, + asyncHandler(RatingController.sortArticlesByRating) +); + +rating.get( + '/rating/:slug/articles', + checkArticleBySlug, + validateArticle.pagination, + asyncHandler(RatingController.ArticleRatings) +); +export default rating; diff --git a/src/routes/api/readingStats.js b/src/routes/api/readingStats.js new file mode 100644 index 00000000..c9889b83 --- /dev/null +++ b/src/routes/api/readingStats.js @@ -0,0 +1,18 @@ +import { Router } from 'express'; +import ReadingStat from '../../controllers/ReadingStatController'; +import verifyToken from '../../middlewares/verifyToken'; +import asyncHandler from '../../middlewares/asyncHandler'; +import checkArticleBySlug from '../../middlewares/checkArticleBySlug'; + +const statsRouter = Router(); + +statsRouter.post( + '/profile/:slug/stats', + verifyToken, + checkArticleBySlug, + asyncHandler(ReadingStat.create) +); + +statsRouter.get('/profile/stats', verifyToken, asyncHandler(ReadingStat.getAll)); + +export default statsRouter; diff --git a/src/routes/api/reports.js b/src/routes/api/reports.js new file mode 100644 index 00000000..52858f29 --- /dev/null +++ b/src/routes/api/reports.js @@ -0,0 +1,52 @@ +import { Router } from 'express'; +import ReportController from '../../controllers/ReportController'; +import checkArticle from '../../middlewares/checkArticle'; +import verifyToken from '../../middlewares/verifyToken'; +import asyncHandler from '../../middlewares/asyncHandler'; +import { checkUserReport, checkReportExist, validateReport } from '../../middlewares/checkReport'; +import checkPermissions from '../../middlewares/checkPermissions'; + +const router = Router(); + +router.post( + '/:articleSlug/report', + verifyToken, + checkArticle, + validateReport, + checkUserReport, + asyncHandler(ReportController.createReport) +); +router.get( + '/:articleSlug/report', + verifyToken, + checkPermissions({ + route: 'articles', + action: 'read' + }), + checkArticle, + asyncHandler(ReportController.getAll) +); +router.get( + '/reports', + verifyToken, + checkPermissions({ + route: 'articles', + action: 'read' + }), + asyncHandler(ReportController.getAll) +); +router.get( + '/:articleSlug/report/:reportId', + verifyToken, + checkArticle, + checkReportExist, + asyncHandler(ReportController.getSingle) +); +router.delete( + '/:articleSlug/report/:reportId', + verifyToken, + checkReportExist, + asyncHandler(ReportController.deleteSingle) +); + +export default router; diff --git a/src/routes/api/roles.js b/src/routes/api/roles.js new file mode 100644 index 00000000..8b08a4bc --- /dev/null +++ b/src/routes/api/roles.js @@ -0,0 +1,18 @@ +import { Router } from 'express'; +import UserController from '../../controllers/UserController'; +import verifyToken from '../../middlewares/verifyToken'; +import checkUpdateUserPermission from '../../middlewares/checkUpdateUserPermission'; +import verifyAdminUser from '../../middlewares/verifyAdmin'; +// import verifyAdminUser from '../../middlewares/verifyAdmin'; + +const rolesRouter = Router(); + +rolesRouter.put( + '/:username', + verifyToken, + checkUpdateUserPermission, + verifyAdminUser, + UserController.updateUserRole +); + +export default rolesRouter; diff --git a/src/routes/api/tags.js b/src/routes/api/tags.js new file mode 100644 index 00000000..80f89a54 --- /dev/null +++ b/src/routes/api/tags.js @@ -0,0 +1,37 @@ +import { Router } from 'express'; +import TagController from '../../controllers/TagController'; +import asyncHandler from '../../middlewares/asyncHandler'; +import validateArticle from '../../middlewares/validation/articles'; +import checkArticleBySlug from '../../middlewares/checkArticleBySlug'; +import validateTags from '../../middlewares/validation/validateTags'; +import verifyToken from '../../middlewares/verifyToken'; +import checkPermissions from '../../middlewares/checkPermissions'; + +const tags = Router(); + +// create article +tags.put( + '/articles/:slug/tags', + verifyToken, + checkArticleBySlug, + validateTags.create, + validateArticle.slug, + checkPermissions({ + route: 'articles', + action: 'edit' + }), + asyncHandler(TagController.update) +); +tags.delete( + '/articles/:slug/tags', + verifyToken, + checkArticleBySlug, + checkPermissions({ + route: 'tags', + action: 'delete' + }), + asyncHandler(TagController.update) +); +tags.get('/tags', asyncHandler(TagController.getAll)); + +export default tags; diff --git a/src/routes/api/upload.js b/src/routes/api/upload.js new file mode 100644 index 00000000..6ab79c57 --- /dev/null +++ b/src/routes/api/upload.js @@ -0,0 +1,33 @@ +import { Router } from 'express'; +import UploadController from '../../controllers/UploadController'; +import verifyToken from '../../middlewares/verifyToken'; +import asyncHandler from '../../middlewares/asyncHandler'; +import multerUploads from '../../middlewares/multerUploads'; +import validateArticle from '../../middlewares/validation/articles'; +import checkArticleBySlug from '../../middlewares/checkArticleBySlug'; +import checkPermissions from '../../middlewares/checkPermissions'; + +const upload = Router(); +upload.post( + '/upload', + verifyToken, + checkPermissions({ + route: 'articles', + action: 'create' + }), + multerUploads.array('image', 1), + asyncHandler(UploadController.save) +); +upload.get('/gallery', verifyToken, validateArticle.pagination, asyncHandler(UploadController.get)); +upload.post( + '/articles/:slug/cover', + verifyToken, + checkArticleBySlug, + checkPermissions({ + route: 'articles', + action: 'edit' + }), + asyncHandler(UploadController.setCover) +); + +export default upload; diff --git a/src/routes/api/users.js b/src/routes/api/users.js new file mode 100644 index 00000000..6a729dc8 --- /dev/null +++ b/src/routes/api/users.js @@ -0,0 +1,68 @@ +import { Router } from 'express'; +import UserController from '../../controllers/UserController'; +import verifyToken from '../../middlewares/verifyToken'; +import validateUser from '../../middlewares/validateUser'; +import checkUpdateUserPermission from '../../middlewares/checkUpdateUserPermission'; +import asyncHandler from '../../middlewares/asyncHandler'; +import AuthLocalController from '../../controllers/AuthLocalController'; +import isActiveUser from '../../middlewares/isActiveUser'; +import verifyAdmin from '../../middlewares/verifyAdmin'; +import checkUpdateUser from '../../middlewares/checkUpdateUser'; +import checkSignUpPermission from '../../middlewares/checkSignUpPermission'; + +const router = Router(); + +router.put( + '/', + verifyToken, + checkUpdateUserPermission, + validateUser, + checkUpdateUser, + UserController.update +); // update user profile + +router.get( + '/username/:username', + verifyToken, + verifyAdmin, + checkUpdateUserPermission, + UserController.getAllByUsername +); +router.get('/email/confirm/:token', verifyToken, UserController.confirmEmailUpdate); // confirm email update +router.get('/authors', verifyToken, asyncHandler(UserController.getAllAuthors)); +router.put( + '/:id', + verifyToken, + validateUser, + isActiveUser, + checkUpdateUserPermission, + checkUpdateUser, + UserController.update +); // update user profile by id + +router.get('/', verifyToken, verifyAdmin, asyncHandler(UserController.getAll)); +// user followers +router.get('/followers', verifyToken, UserController.followers); + +// user following +router.get('/following', verifyToken, UserController.following); +router.delete( + '/:id', + verifyToken, + checkUpdateUserPermission, + isActiveUser, + AuthLocalController.deactivateAccount +); +router.patch('/:username/unfollow', verifyToken, UserController.unfollow); + +router.get('/:id', verifyToken, verifyAdmin, checkUpdateUserPermission, AuthLocalController.getOne); +router.post( + '/', + verifyToken, + verifyAdmin, + validateUser, + checkSignUpPermission, + AuthLocalController.create +); +router.patch('/:username/follow', verifyToken, isActiveUser, UserController.follow); +export default router; diff --git a/src/routes/auth/facebook.js b/src/routes/auth/facebook.js new file mode 100644 index 00000000..b37c12e9 --- /dev/null +++ b/src/routes/auth/facebook.js @@ -0,0 +1,17 @@ +import express from 'express'; +import passport from '../../middlewares/passport'; +import AuthPassportController from '../../controllers/AuthPassportController'; +import checkSignUpPermission from '../../middlewares/checkSignUpPermission'; + +const router = express.Router(); + +router.get('/', passport.authenticate('facebook', { scope: ['email'] })); + +router.get( + '/callback', + passport.authenticate('facebook'), + checkSignUpPermission, + AuthPassportController.loginOrSignup +); + +export default router; diff --git a/src/routes/auth/google.js b/src/routes/auth/google.js new file mode 100644 index 00000000..9a9ffe89 --- /dev/null +++ b/src/routes/auth/google.js @@ -0,0 +1,17 @@ +import express from 'express'; +import passport from '../../middlewares/passport'; +import AuthPassportController from '../../controllers/AuthPassportController'; +import checkSignUpPermission from '../../middlewares/checkSignUpPermission'; + +const router = express.Router(); + +router.get('/', passport.authenticate('google', { scope: ['email', 'profile'] })); + +router.get( + '/callback', + passport.authenticate('google'), + checkSignUpPermission, + AuthPassportController.loginOrSignup +); + +export default router; diff --git a/src/routes/auth/index.js b/src/routes/auth/index.js new file mode 100644 index 00000000..43b8d505 --- /dev/null +++ b/src/routes/auth/index.js @@ -0,0 +1,29 @@ +import express from 'express'; +import local from './local'; +import facebook from './facebook'; +import twitter from './twitter'; +import google from './google'; +import logout from '../../middlewares/logout'; +import verifyToken from '../../middlewares/verifyToken'; +import AuthLocalController from '../../controllers/AuthLocalController'; +import status from '../../config/status'; + +const router = express.Router(); + +router.use('/', local); +router.use('/facebook', facebook); +router.use('/twitter', twitter); +router.use('/google', google); +router.get('/logout', verifyToken, logout); +router.get( + '/:id', + verifyToken, + (req, res, next) => (req.user.id === (req.params && Number.parseInt(req.params.id, 10)) + ? next() + : res + .status(status.UNAUTHORIZED) + .json({ errors: { user: "Sorry, this account doesn't exist" } })), + AuthLocalController.getOne +); + +export default router; diff --git a/src/routes/auth/local.js b/src/routes/auth/local.js new file mode 100644 index 00000000..9662b546 --- /dev/null +++ b/src/routes/auth/local.js @@ -0,0 +1,30 @@ +import express from 'express'; +import AuthLocalController from '../../controllers/AuthLocalController'; +import asyncHandler from '../../middlewares/asyncHandler'; +import verifyToken from '../../middlewares/verifyToken'; +import validateUser from '../../middlewares/validateUser'; +import validateLogin from '../../middlewares/validateLogin'; +import isActiveUser from '../../middlewares/isActiveUser'; +import checkSingUpPermission from '../../middlewares/checkSignUpPermission'; + +const router = express.Router(); + +router.post( + '/signup', + checkSingUpPermission, + validateUser, + asyncHandler(AuthLocalController.signup) +); + +// user login route +router.post('/login', validateLogin, isActiveUser, AuthLocalController.login); + +// activate user account +router.get('/activate/:token', verifyToken, AuthLocalController.activate); + +// Reset password +router.get('/reset/:token', asyncHandler(AuthLocalController.reset)); +router.post('/reset', asyncHandler(AuthLocalController.sendEmail)); +router.patch('/reset/:token', asyncHandler(AuthLocalController.updatePassword)); + +export default router; diff --git a/src/routes/auth/twitter.js b/src/routes/auth/twitter.js new file mode 100644 index 00000000..5188abef --- /dev/null +++ b/src/routes/auth/twitter.js @@ -0,0 +1,17 @@ +import express from 'express'; +import passport from '../../middlewares/passport'; +import AuthPassportController from '../../controllers/AuthPassportController'; +import checkSignUpPermission from '../../middlewares/checkSignUpPermission'; + +const router = express.Router(); + +router.get('/', passport.authenticate('twitter', { scope: ['email', 'profile'] })); + +router.get( + '/callback', + passport.authenticate('twitter'), + checkSignUpPermission, + AuthPassportController.loginOrSignup +); + +export default router; diff --git a/src/routes/index.js b/src/routes/index.js new file mode 100644 index 00000000..80293ece --- /dev/null +++ b/src/routes/index.js @@ -0,0 +1,10 @@ +import express from 'express'; +import auth from './auth'; +import api from './api'; + +const router = express.Router(); + +router.use('/auth', auth); +router.use(api); + +export default router; diff --git a/src/seeders/20190605075225-permissions.js b/src/seeders/20190605075225-permissions.js new file mode 100644 index 00000000..8cc3a3b8 --- /dev/null +++ b/src/seeders/20190605075225-permissions.js @@ -0,0 +1,14 @@ +import * as Factory from '../helpers/factory'; + +const permissionsNormal = Factory.permissionsNormal.build(); +const permissionsAdmin = Factory.permissionsAdmin.build(); +permissionsNormal.createdAt = new Date(); +permissionsNormal.updatedAt = new Date(); +permissionsAdmin.createdAt = new Date(); +permissionsAdmin.updatedAt = new Date(); + +export default { + up: queryInterface => queryInterface.bulkInsert('Permissions', [permissionsNormal, permissionsAdmin], {}), + + down: queryInterface => queryInterface.bulkDelete('Permissions', null, {}) +}; diff --git a/src/seeders/20190715123639-admin-user.js b/src/seeders/20190715123639-admin-user.js new file mode 100644 index 00000000..75b049da --- /dev/null +++ b/src/seeders/20190715123639-admin-user.js @@ -0,0 +1,21 @@ +import * as Factory from '../helpers/factory'; +import { password } from '../helpers'; + +const userAdmin = { + firstName: 'admin', + lastName: 'admin', + username: 'admin', + email: 'admin@admin.admin', + password: password.hash('admin'), + role: 'admin', + isActive: true, + permissions: Factory.permissionsAdmin.build().permissions, + createdAt: new Date(), + updatedAt: new Date() +}; + +export default { + up: queryInterface => queryInterface.bulkInsert('Users', [userAdmin], {}), + + down: queryInterface => queryInterface.bulkDelete('Users', null, {}) +}; diff --git a/src/tests/config/cloudinaryConfig.test.js b/src/tests/config/cloudinaryConfig.test.js new file mode 100644 index 00000000..0c64cbaf --- /dev/null +++ b/src/tests/config/cloudinaryConfig.test.js @@ -0,0 +1,21 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import chai from 'chai'; +import chaiHttp from 'chai-http'; +import dotenv from 'dotenv'; +import cloudinaryConfig from '../../config/cloudinaryConfig'; + +dotenv.config(); +const { expect } = chai; + +chai.use(chaiHttp); + +describe('Cloudinary config', () => { + it('should return api information from the callback', (done) => { + const response = cloudinaryConfig(); + expect(response).to.be.an('object'); + response.cloud_name.should.be.a('string'); + response.api_key.should.be.a('string'); + response.api_secret.should.be.a('string'); + done(); + }); +}); diff --git a/src/tests/config/index.js b/src/tests/config/index.js new file mode 100644 index 00000000..bf517d75 --- /dev/null +++ b/src/tests/config/index.js @@ -0,0 +1,3 @@ +import './passportConfig.test'; +import './cloudinaryConfig.test'; +import './permissions.test'; diff --git a/src/tests/config/passportConfig.test.js b/src/tests/config/passportConfig.test.js new file mode 100644 index 00000000..248347cf --- /dev/null +++ b/src/tests/config/passportConfig.test.js @@ -0,0 +1,58 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import chai from 'chai'; +import chaiHttp from 'chai-http'; +import * as Factory from '../../helpers/factory'; +import passportConfig from '../../config/passportConfig'; + +const { expect } = chai; + +chai.use(chaiHttp); + +const done = (err, profile) => profile; +const [user, profile] = [Factory.user.build(), Factory.user.build()]; +const [accessToken, token] = ['agdy7536dg', 'agdy7536dg']; +const [refreshToken, tokenSecret] = ['agdy7536dg', 'agdy7536dg']; + +describe('Passport config', () => { + describe('FacebookStrategy', () => { + it('should return user information from the callback', () => { + const result = passportConfig.facebook.callbackFunc(accessToken, refreshToken, profile, done); + expect(Object.keys(result).length).to.be.above(0); + }); + }); + + describe('TwitterStrategy', () => { + it('should return user information from the callback', () => { + const result = passportConfig.twitter.callbackFunc(token, tokenSecret, profile, done); + expect(Object.keys(result).length).to.be.above(0); + }); + }); + + describe('OAuthStrategy', () => { + it('should return user information from callback', () => { + const result = passportConfig.google.callbackFunc(accessToken, refreshToken, profile, done); + expect(Object.keys(result).length).to.be.above(0); + }); + }); + + describe('OAuth2Strategy', () => { + it('should return user information from callback', () => { + const result = passportConfig.google2.callbackFunc(accessToken, refreshToken, profile, done); + expect(Object.keys(result).length).to.be.above(0); + }); + }); + + describe('Serialize user information', () => { + it('should serialize user information', () => { + const result = passportConfig.serializeUser(user, done); + expect(Object.keys(result).length).to.be.above(0); + }); + }); + + describe('Deserialize user information', () => { + it('should deserialize user information', () => { + const result = passportConfig.deserializeUser(user, done); + expect(Object.keys(result).length).to.be.above(0); + }); + }); +}); diff --git a/src/tests/config/permissions.test.js b/src/tests/config/permissions.test.js new file mode 100644 index 00000000..1c0746f3 --- /dev/null +++ b/src/tests/config/permissions.test.js @@ -0,0 +1,28 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import chai from 'chai'; +import chaiHttp from 'chai-http'; +import db from '../../models'; +import * as Factory from '../../helpers/factory'; +import permissions from '../../config/permissions'; + +const { expect } = chai; + +chai.use(chaiHttp); + +const newPermissionNormal = Factory.permissionsNormal.build(); +const newPermissionAdmin = Factory.permissionsAdmin.build(); + +describe('Permissions', () => { + before(async () => { + await db.Permission.destroy({ + truncate: true, + cascade: true, + logging: false + }); + await db.Permission.create(newPermissionNormal, { logging: false }); + await db.Permission.create(newPermissionAdmin, { logging: false }); + }); + it('should return all permissions', async () => { + const allPermissions = await permissions(); + }); +}); diff --git a/src/tests/controllers/ArticleController.test.js b/src/tests/controllers/ArticleController.test.js new file mode 100644 index 00000000..c43b1b14 --- /dev/null +++ b/src/tests/controllers/ArticleController.test.js @@ -0,0 +1,634 @@ +import chai from 'chai'; +import chaiHttp from 'chai-http'; +import jwt from 'jsonwebtoken'; +import app from '../../app'; +import status from '../../config/status'; +import db from '../../models'; +import * as Factory from '../../helpers/factory'; + +const { expect } = chai; +chai.should(); + +const article = Factory.article.build(); +chai.use(chaiHttp); + +let accessToken = ''; +let createdUser = ''; + +const user = Factory.user.build(); + +delete user.id; +delete article.id; +let author = 0; +describe('No articles', () => { + before(async () => { + createdUser = (await db.User.create(user, { logging: false })).dataValues; + article.userId = createdUser.id; + author = createdUser.id; + accessToken = jwt.sign( + { id: createdUser.id, role: createdUser.role, permissions: createdUser.permissions }, + process.env.SECRET_KEY, + { expiresIn: '1d' } + ); + await db.Article.destroy({ + truncate: true, + cascade: true, + restartIdentity: true, + logging: false + }); + }); + + describe('Test not-found articles', () => { + after(async () => { + const initalArticle = article; + delete initalArticle.id; + initalArticle.userId = author; + delete initalArticle.readTime; + await db.Article.create(initalArticle, { logging: false }); + }); + it('Should test slug validator and reject request if slug characters are minimum to 10', (done) => { + chai + .request(app) + .put('/api/v1/articles/foo/publish') + .set('access-token', accessToken) + .end((err, res) => { + expect(res).to.have.status(status.BAD_REQUEST); + res.body.should.be.an('object'); + res.body.errors.should.be.an('array'); + done(); + }); + }); + it('Should not get articles if article table is empty', (done) => { + chai + .request(app) + .get('/api/v1/articles') + .set('access-token', accessToken) + .end((err, res) => { + expect(res).to.have.status(status.NOT_FOUND); + res.body.should.be.an('object'); + res.body.message.should.equal('No articles found'); + done(); + }); + }); + it('Should not get articles if table is empty', (done) => { + chai + .request(app) + .get(`/api/v1/articles/${article.slug}`) + .set('access-token', accessToken) + .end((err, res) => { + expect(res).to.have.status(status.NOT_FOUND); + res.body.should.be.an('object'); + res.body.error.should.equal('No article found'); + done(); + }); + }); + + it('Should not update if author does not exist', (done) => { + delete article.id; + const fakeArticle1 = { ...article, slug: 'fake-easy-opojvbldff' }; + delete fakeArticle1.userId; + + chai + .request(app) + .put('/api/v1/articles/invalid-bad-pkljvbm1e9o') + .set('access-token', accessToken) + .send(fakeArticle1) + .end((err, res) => { + expect(res).to.have.status(status.BAD_REQUEST); + res.body.should.be.an('object'); + res.body.errors.should.be.an('array'); + done(); + }); + }); + it('Should not update if slug article is not valid', (done) => { + delete article.id; + const fakeArticle1 = { ...article, slug: 'fake-easy-opojvbldff' }; + delete fakeArticle1.userId; + + chai + .request(app) + .put('/api/v1/articles/invalid-bad-pkljvbm1e9o') + .set('access-token', accessToken) + .send(fakeArticle1) + .end((err, res) => { + expect(res).to.have.status(status.BAD_REQUEST); + res.body.should.be.an('object'); + res.body.errors.should.be.an('array'); + done(); + }); + }); + it('Should not unpublish a non-existing article', (done) => { + chai + .request(app) + .put('/api/v1/articles/invali-6jv9cn4szf/unpublish') + .set('access-token', accessToken) + .end((err, res) => { + expect(res).to.have.status(status.NOT_FOUND); + res.body.should.be.a('object'); + res.body.error.should.equal('No article found'); + done(); + }); + }); + it('Should not publish a non-existing article', (done) => { + chai + .request(app) + .put('/api/v1/articles/invalid-1632jv5quc9c/publish') + .set('access-token', accessToken) + .end((err, res) => { + expect(res).to.have.status(status.NOT_FOUND); + res.body.should.be.a('object'); + res.body.error.should.equal('No article found'); + done(); + }); + }); + }); +}); + +describe('Article', () => { + let articleSlug = ''; + before(async () => { + const { dataValues } = await db.Article.findOne({ where: { id: 1 } }, { logging: false }); + articleSlug = dataValues.slug; + // create gallery + await db.Gallery.create({ image: 'placeholder.png', userId: author }); + }); + // create article + it('Should successfully create an article', (done) => { + delete article.id; + delete article.slug; + delete article.readTime; + chai + .request(app) + .post('/api/v1/articles') + .set('access-token', accessToken) + .send(article) + .end((err, res) => { + const response = res.body; + expect(res).to.have.status(status.CREATED); + response.article.status.should.be.a('string'); + response.article.title.should.be.a('string'); + response.article.body.should.be.a('string'); + response.article.slug.should.be.a('string'); + expect(response.article.userId).should.be.an('object'); + done(); + }); + }); + + it('Should publish an article', (done) => { + chai + .request(app) + .put(`/api/v1/articles/${articleSlug}/publish`) + .set('access-token', accessToken) + .end((err, res) => { + expect(res).to.have.status(status.OK); + res.body.should.be.a('object'); + res.body.message.should.equal('Article has been published'); + done(); + }); + }); + it('Should update article cover', async () => { + chai + .request(app) + .post(`/api/v1/articles/${articleSlug}/cover`) + .set('access-token', accessToken) + .send({ coverUrl: 'placeholder.png' }) + .end((err, res) => { + expect(res).to.have.status(status.OK); + res.body.should.be.an('object'); + expect(res.body.coverUrl).to.equal('CoverUrl has been updated'); + }); + }); + it('Should not update article cover if image not provided ', async () => { + chai + .request(app) + .post('/api/v1/articles/rosie-make-it-easy-1dh6jv9cn4sz/cover') + .set('access-token', accessToken) + .end((err, res) => { + expect(res).to.have.status(status.BAD_REQUEST); + res.body.should.be.an('object'); + expect(res.body.errors.coverUrl).to.equal('coverUrl not updated, try again'); + }); + }); + it('Should get gallery', async () => { + chai + .request(app) + .get('/api/v1/gallery') + .set('access-token', accessToken) + .end((err, res) => { + expect(res).to.have.status(status.OK); + res.body.should.be.an('object'); + }); + }); + it('Should successfully create an article even if title is more than 70 characters', (done) => { + delete article.id; + delete article.userId; + delete article.slug; + delete article.readTime; + const bigArticleTitle = { + ...article, + title: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas viverra massa eget turpis finibus dictum' + }; + chai + .request(app) + .post('/api/v1/articles') + .send(bigArticleTitle) + .set('access-token', accessToken) + .end((err, res) => { + const response = res.body; + expect(res).to.have.status(status.CREATED); + response.article.status.should.be.a('string'); + response.article.title.should.be.a('string'); + response.article.body.should.be.a('string'); + response.article.slug.should.be.a('string'); + expect(response.article.userId).should.be.an('object'); + done(); + }); + }); + it('Should not create article if title is empty', (done) => { + delete article.id; + delete article.slug; + delete article.readTime; + article.title = ''; + chai + .request(app) + .post('/api/v1/articles') + .send(article) + .set('access-token', accessToken) + .end((err, res) => { + expect(res).to.have.status(status.BAD_REQUEST); + res.body.errors.should.be.a('array'); + done(); + }); + }); + it('Title should be between 5 to 256 characters', (done) => { + delete article.id; + delete article.slug; + delete article.userId; + delete article.readTime; + article.title = 'Hey'; + chai + .request(app) + .post('/api/v1/articles') + .send(article) + .set('access-token', accessToken) + .end((err, res) => { + expect(res).to.have.status(status.BAD_REQUEST); + res.body.errors.should.be.a('array'); + done(); + }); + }); + it('Should trim empty space before the first letter of the title and after the last letter of the title', (done) => { + delete article.id; + delete article.slug; + delete article.readTime; + article.title = ' Rosie, make it easy '; + const fakeArticle2 = { ...article, userId: author }; + chai + .request(app) + .post('/api/v1/articles') + .send(fakeArticle2) + .set('access-token', accessToken) + .end((err, res) => { + const response = res.body; + expect(res).to.have.status(status.CREATED); + response.article.status.should.be.a('string'); + response.article.title.should.be.a('string'); + response.article.body.should.be.a('string'); + response.article.slug.should.be.a('string'); + expect(response.article.userId).should.be.an('object'); + done(); + }); + }); + it('Should not create article if body is empty', (done) => { + delete article.id; + delete article.slug; + delete article.readTime; + article.body = ''; + chai + .request(app) + .post('/api/v1/articles') + .set(article) + .set('access-token', accessToken) + .end((err, res) => { + expect(res).to.have.status(400); + res.body.errors.should.be.a('array'); + done(); + }); + }); + it('Should not create the article if the content is less than 5 characters', (done) => { + delete article.id; + delete article.slug; + delete article.userId; + delete article.readTime; + article.body = 'Hey'; + chai + .request(app) + .post('/api/v1/articles') + .send(article) + .set('access-token', accessToken) + .end((err, res) => { + expect(res).to.have.status(status.BAD_REQUEST); + res.body.errors.should.be.a('array'); + done(); + }); + }); + it('Should trim empty spaces before the content of the article and after the last letter of the content', (done) => { + delete article.id; + delete article.slug; + delete article.readTime; + article.body = ' More about this the dance of chicken '; + const fakeArticle3 = { ...article, userId: author }; + chai + .request(app) + .post('/api/v1/articles') + .set('access-token', accessToken) + .send(fakeArticle3) + .end((err, res) => { + const response = res.body; + expect(res).to.have.status(status.CREATED); + response.article.status.should.be.a('string'); + response.article.title.should.be.a('string'); + response.article.body.should.be.a('string'); + response.article.slug.should.be.a('string'); + expect(response.article.userId).should.be.an('object'); + done(); + }); + }); + it('Should get all articles', (done) => { + chai + .request(app) + .get('/api/v1/articles') + .set('access-token', accessToken) + .end((err, res) => { + const response = res.body; + expect(res).to.have.status(status.OK); + response.articles.should.be.a('array'); + response.articles[0].should.be.a('object'); + response.articles[0].id.should.be.a('number'); + response.articles[0].title.should.be.a('string'); + response.articles[0].body.should.be.a('string'); + response.articles[0].description.should.be.a('string'); + response.articles[0].slug.should.be.a('string'); + done(); + }); + }); + it('Should get all articles', (done) => { + chai + .request(app) + .get('/api/v1/articles/drafts') + .set('access-token', accessToken) + .end((err, res) => { + const response = res.body; + expect(res).to.have.status(status.OK); + response.articles.should.be.a('array'); + response.articles[0].should.be.a('object'); + response.articles[0].id.should.be.a('number'); + response.articles[0].title.should.be.a('string'); + response.articles[0].body.should.be.a('string'); + response.articles[0].description.should.be.a('string'); + response.articles[0].slug.should.be.a('string'); + done(); + }); + }); + it('Should get articles by pagination', (done) => { + const LIMIT = 1; + const OFFSET = 0; + chai + .request(app) + .get(`/api/v1/articles?limit=${LIMIT}&offset=${OFFSET}`) + .set('access-token', accessToken) + .end((err, res) => { + const response = res.body; + expect(res).to.have.status(status.OK); + response.articles.should.be.a('array'); + response.articles[0].should.be.a('object'); + response.articles[0].id.should.be.a('number'); + response.articles[0].title.should.be.a('string'); + response.articles[0].body.should.be.a('string'); + response.articles[0].description.should.be.a('string'); + response.articles[0].slug.should.be.a('string'); + done(); + }); + }); + it('Should throw error if limit is a string', (done) => { + const LIMIT = 'text'; + const OFFSET = 0; + chai + .request(app) + .get(`/api/v1/articles?limit=${LIMIT}&offset=${OFFSET}`) + .set('access-token', accessToken) + .end((err, res) => { + expect(res).to.have.status(status.BAD_REQUEST); + res.body.errors.should.be.an('array'); + res.body.errors[0].should.equal('limit must be a number'); + done(); + }); + }); + it('Should throw error if tag filter is called more than once', (done) => { + const TAG = 'text'; + chai + .request(app) + .get(`/api/v1/articles?tag=${TAG}&tag=${TAG}`) + .set('access-token', accessToken) + .end((err, res) => { + expect(res).to.have.status(status.BAD_REQUEST); + res.body.errors.should.be.an('object'); + res.body.errors.tag.should.be.a('string'); + res.body.errors.tag.should.equal('tag can not be declared more than once'); + done(); + }); + }); + it('Should throw error if keyword filter is called more than once', (done) => { + const KEYWORD = 'text'; + chai + .request(app) + .get(`/api/v1/articles?keyword=${KEYWORD}&keyword=${KEYWORD}`) + .set('access-token', accessToken) + .end((err, res) => { + expect(res).to.have.status(status.BAD_REQUEST); + res.body.errors.should.be.an('object'); + res.body.errors.keyword.should.be.a('string'); + res.body.errors.keyword.should.equal('keyword can not be declared more than once'); + done(); + }); + }); + it('Should throw error if author filter is called more than once', (done) => { + const AUTHOR = 'text'; + chai + .request(app) + .get(`/api/v1/articles?author=${AUTHOR}&author=${AUTHOR}`) + .set('access-token', accessToken) + .end((err, res) => { + expect(res).to.have.status(status.BAD_REQUEST); + res.body.errors.should.be.an('object'); + res.body.errors.author.should.be.a('string'); + res.body.errors.author.should.equal('author can not be declared more than once'); + done(); + }); + }); + it('Should throw error if offset is a string', (done) => { + const LIMIT = 1; + const OFFSET = 'text'; + chai + .request(app) + .get(`/api/v1/articles?limit=${LIMIT}&offset=${OFFSET}`) + .set('access-token', accessToken) + .end((err, res) => { + expect(res).to.have.status(status.BAD_REQUEST); + res.body.errors.should.be.an('array'); + res.body.errors[0].should.equal('offset must be a number'); + done(); + }); + }); + it('Should throw error if offset number is less than 0', (done) => { + const LIMIT = 1; + const OFFSET = -3; + chai + .request(app) + .get(`/api/v1/articles?limit=${LIMIT}&offset=${OFFSET}`) + .set('access-token', accessToken) + .end((err, res) => { + expect(res).to.have.status(status.BAD_REQUEST); + res.body.errors.should.be.a('array'); + res.body.errors[0].should.equal('offset must be larger than or equal to 0'); + done(); + }); + }); + it('should throw error if limit is declared 2 times', (done) => { + const LIMIT = 1; + chai + .request(app) + .get(`/api/v1/articles?limit=${LIMIT}&limit=${LIMIT}`) + .set('access-token', accessToken) + .end((err, res) => { + expect(res).to.have.status(status.BAD_REQUEST); + res.body.errors.should.be.a('object'); + res.body.errors.should.have.property('limit'); + res.body.errors.limit.should.equal('limit can not be declared more than once'); + done(); + }); + }); + it('should throw error if offset is declared 2 times', (done) => { + const OFFSET = 1; + chai + .request(app) + .get(`/api/v1/articles?offset=${OFFSET}&offset=${OFFSET}`) + .set('access-token', accessToken) + .end((err, res) => { + expect(res).to.have.status(status.BAD_REQUEST); + res.body.errors.should.be.a('object'); + res.body.errors.should.have.property('offset'); + res.body.errors.offset.should.equal('offset can not be declared more than once'); + done(); + }); + }); + it('Should throw error if limit number is less than 1', (done) => { + const LIMIT = 0; + const OFFSET = 0; + chai + .request(app) + .get(`/api/v1/articles?limit=${LIMIT}&offset=${OFFSET}`) + .set('access-token', accessToken) + .end((err, res) => { + expect(res).to.have.status(status.BAD_REQUEST); + res.body.errors.should.be.a('array'); + res.body.errors[0].should.equal('limit must be larger than or equal to 1'); + done(); + }); + }); + it('Should get one article', (done) => { + chai + .request(app) + .get(`/api/v1/articles/${articleSlug}`) + .set('access-token', accessToken) + .end((err, res) => { + const response = res.body; + expect(res).to.have.status(status.OK); + response.article.should.be.a('object'); + response.article.id.should.be.a('number'); + response.article.title.should.be.a('string'); + response.article.body.should.be.a('string'); + response.article.description.should.be.a('string'); + response.article.slug.should.be.a('string'); + response.article.coverUrl.should.be.a('string'); + done(); + }); + }); + it('Should update the article', (done) => { + delete article.id; + const updateArticle = { ...article }; + delete updateArticle.slug; + delete updateArticle.tagList; + delete updateArticle.readTime; + chai + .request(app) + .put(`/api/v1/articles/${articleSlug}`) + .send(updateArticle) + .set('access-token', accessToken) + .end((err, res) => { + expect(res).to.have.status(status.OK); + res.body.should.be.an('object'); + res.body.message.should.equal('Article has been updated'); + done(); + }); + }); + it('Should test slug validator and reject request if slug characters are minimum to 10', (done) => { + const smallSlug = { ...article, title: 'Hey' }; + chai + .request(app) + .post('/api/v1/articles') + .set('access-token', accessToken) + .send(smallSlug) + .end((err, res) => { + expect(res).to.have.status(status.BAD_REQUEST); + res.body.should.be.an('object'); + res.body.errors.should.be.a('array'); + done(); + }); + }); + it('Should unpublish an article', (done) => { + const unpublish = { ...article, slug: 'rosie-make-it-easy-1dh6jv9cn4sz' }; + chai + .request(app) + .put(`/api/v1/articles/${articleSlug}/unpublish`) + .set('access-token', accessToken) + .end((err, res) => { + expect(res).to.have.status(status.OK); + res.body.should.be.a('object'); + res.body.message.should.equal('Article has been unpublished'); + done(); + }); + }); + + it('Should delete article', (done) => { + chai + .request(app) + .delete(`/api/v1/articles/${articleSlug}`) + .set('access-token', accessToken) + .end((err, res) => { + expect(res).to.have.status(status.OK); + res.body.should.be.an('object'); + res.body.message.should.equal('Article has been deleted'); + done(); + }); + }); + it('Should not create article if the author does not exist', (done) => { + delete article.id; + delete article.slug; + delete article.readTime; + const fakeArticle = { ...article, userId: 28768879 }; + chai + .request(app) + .post('/api/v1/articles') + .send(fakeArticle) + .set('access-token', 'brabra') + .end((err, res) => { + expect(res).to.have.status(status.UNAUTHORIZED); + res.body.should.be.an('object'); + res.body.should.have.property('errors'); + res.body.errors.token.should.equal('Failed to authenticate token'); + done(); + }); + }); +}); diff --git a/src/tests/controllers/AuthPassportController.test.js b/src/tests/controllers/AuthPassportController.test.js new file mode 100644 index 00000000..928c872a --- /dev/null +++ b/src/tests/controllers/AuthPassportController.test.js @@ -0,0 +1,188 @@ +/* eslint-disable no-unused-expressions */ +/* eslint-disable import/no-extraneous-dependencies */ +import chai from 'chai'; +import chaiHttp from 'chai-http'; +import express from 'express'; +import 'dotenv/config'; +import db from '../../models'; +import * as Factory from '../../helpers/factory'; +import AuthPassportController from '../../controllers/AuthPassportController'; + +const { expect } = chai; + +chai.use(chaiHttp); + +const app = express(); +const router = express.Router(); + +app.use(express.json()); +app.use( + express.urlencoded({ + extended: false + }) +); + +app.use( + '/api/v1/auth', + (req, res, next) => { + req.user = req.body; + next(); + }, + router.post('/', AuthPassportController.loginOrSignup) +); + +const userFacebook = Factory.userFacebook.build(); +const userTwitter = Factory.userTwitter.build(); +const userGoogle = Factory.userGoogle.build(); + +describe('Passport Authentication controller', () => { + before(async () => { + try { + await db.User.destroy({ + truncate: true, + cascade: true, + logging: false + }); + } catch (error) { + throw error; + } + }); + + it('should not create a user if some required fields are not provided', (done) => { + chai + .request(app) + .post('/api/v1/auth') + .send(Factory.userFacebook.build({ name: { familyName: null, givenName: null } })) + .end((err, res) => { + expect(res).to.redirect; + expect(res.redirects[0].indexOf('code=400')).to.be.above(0); + done(); + }); + }); + + it('should register a facebook user', (done) => { + chai + .request(app) + .post('/api/v1/auth') + .send(userFacebook) + .end((err, res) => { + expect(res).to.redirect; + expect(res.redirects[0].indexOf('id=')).to.be.above(0); + expect(res.redirects[0].indexOf('token=')).to.be.above(0); + done(); + }); + }); + + it('should login a facebook user', (done) => { + chai + .request(app) + .post('/api/v1/auth') + .send(userFacebook) + .end((err, res) => { + expect(res).to.redirect; + expect(res.redirects[0].indexOf('id=')).to.be.above(0); + expect(res.redirects[0].indexOf('token=')).to.be.above(0); + done(); + }); + }); + + it('should register a twitter user', (done) => { + chai + .request(app) + .post('/api/v1/auth') + .send(userTwitter) + .end((err, res) => { + expect(res).to.redirect; + expect(res.redirects[0].indexOf('id=')).to.be.above(0); + expect(res.redirects[0].indexOf('token=')).to.be.above(0); + done(); + }); + }); + + it('should login a twitter user', (done) => { + chai + .request(app) + .post('/api/v1/auth') + .send(userTwitter) + .end((err, res) => { + expect(res).to.redirect; + expect(res.redirects[0].indexOf('id=')).to.be.above(0); + expect(res.redirects[0].indexOf('token=')).to.be.above(0); + done(); + }); + }); + + it('should register a google user', (done) => { + chai + .request(app) + .post('/api/v1/auth') + .send(userGoogle) + .end((err, res) => { + expect(res).to.redirect; + expect(res.redirects[0].indexOf('id=')).to.be.above(0); + expect(res.redirects[0].indexOf('token=')).to.be.above(0); + done(); + }); + }); + + it('should login a google user', (done) => { + chai + .request(app) + .post('/api/v1/auth') + .send(userGoogle) + .end((err, res) => { + expect(res).to.redirect; + expect(res.redirects[0].indexOf('id=')).to.be.above(0); + expect(res.redirects[0].indexOf('token=')).to.be.above(0); + done(); + }); + }); + + it('should not register a user if the email is already used', (done) => { + chai + .request(app) + .post('/api/v1/auth') + .send({ ...userFacebook, provider: 'another', username: 'username' }) + .end((err, res) => { + expect(res).to.redirect; + expect(res.redirects[0].indexOf('email=409')).to.be.above(0); + done(); + }); + }); + + it('should not register a user if username is already used', (done) => { + chai + .request(app) + .post('/api/v1/auth') + .send({ ...userTwitter, provider: 'another' }) + .end((err, res) => { + expect(res).to.redirect; + expect(res.redirects[0].indexOf('username=409')).to.be.above(0); + done(); + }); + }); + + it('should not create a user if the body is empty', (done) => { + chai + .request(app) + .post('/api/v1/auth') + .send({}) + .end((err, res) => { + expect(res).to.redirect; + expect(res.redirects[0].indexOf('code=400')).to.be.above(0); + done(); + }); + }); + + it('should not create a user if some inputs are not correct', (done) => { + chai + .request(app) + .post('/api/v1/auth') + .send(Factory.userFacebook.build({ provider: {} })) + .end((err, res) => { + expect(res).to.redirect; + expect(res.redirects[0].indexOf('code=500')).to.be.above(0); + done(); + }); + }); +}); diff --git a/src/tests/controllers/RatingController.test.js b/src/tests/controllers/RatingController.test.js new file mode 100644 index 00000000..fcc02249 --- /dev/null +++ b/src/tests/controllers/RatingController.test.js @@ -0,0 +1,155 @@ +import chai from 'chai'; +import chaiHttp from 'chai-http'; +import jwt from 'jsonwebtoken'; +import app from '../../app'; +import status from '../../config/status'; +import db from '../../models'; +import * as helpers from '../../helpers'; +import * as Factory from '../../helpers/factory'; + +const { expect } = chai; +chai.should(); +chai.use(chaiHttp); + +let createdArticle = ''; +let userId = 0; +let article = helpers.factory.article.build(); +delete article.id; +delete article.tagList; +const RATING = 3; + +let accessToken = ''; +let createdUser = ''; + +const user = Factory.user.build(); + +delete user.id; +delete article.id; +user.email = 'rating@haven.com'; +user.username = 'rating123'; +describe('ARTICLE RATINGS', () => { + before(async () => { + createdUser = (await db.User.create(user, { logging: false })).dataValues; + userId = createdUser.id; + accessToken = jwt.sign( + { id: createdUser.id, role: createdUser.role, permissions: createdUser.permissions }, + process.env.SECRET_KEY, + { expiresIn: '1d' } + ); + article = { ...article, userId, slug: helpers.generator.slug(article.slug) }; + createdArticle = await db.Article.create(article, { logging: false }); + + return createdArticle; + }); + it('should create rating', (done) => { + const data = { + rating: RATING + }; + chai + .request(app) + .post(`/api/v1/rating/${createdArticle.dataValues.slug}/article`) + .send(data) + .set('access-token', accessToken) + .end((err, res) => { + expect(res).to.have.status(status.CREATED); + res.body.should.be.an('object'); + res.body.rating.message.should.equal('Thank you for rating this article'); + done(); + }); + }); + it('should update rating', (done) => { + const data = { + rating: RATING + }; + chai + .request(app) + .post(`/api/v1/rating/${createdArticle.dataValues.slug}/article`) + .send(data) + .set('access-token', accessToken) + .end((err, res) => { + expect(res).to.have.status(status.OK); + res.body.should.be.an('object'); + res.body.rating.message.should.equal('Your article rating has been updated'); + done(); + }); + }); + it('should not create rating if it is greater than 5', (done) => { + const data = { + rating: 6 + }; + chai + .request(app) + .post(`/api/v1/rating/${createdArticle.dataValues.slug}/article`) + .send(data) + .set('access-token', accessToken) + .end((err, res) => { + expect(res).to.have.status(status.BAD_REQUEST); + res.body.should.be.an('object'); + res.body.errors.rating.should.equal('rating must be between 1 and 5'); + done(); + }); + }); + it('should not create rating if it is less than 1', (done) => { + const data = { + rating: 0 + }; + chai + .request(app) + .post(`/api/v1/rating/${createdArticle.dataValues.slug}/article`) + .send(data) + .set('access-token', accessToken) + .end((err, res) => { + expect(res).to.have.status(status.BAD_REQUEST); + res.body.should.be.an('object'); + res.body.errors.rating.should.equal('rating must be between 1 and 5'); + done(); + }); + }); + it('should not create rating if rating is not a number', (done) => { + const data = { + rating: 'one' + }; + chai + .request(app) + .post(`/api/v1/rating/${createdArticle.dataValues.slug}/article`) + .send(data) + .set('access-token', accessToken) + .end((err, res) => { + expect(res).to.have.status(status.BAD_REQUEST); + res.body.should.be.an('object'); + res.body.errors.rating.should.equal('rating must be a number'); + done(); + }); + }); + it("should get article's rating", (done) => { + chai + .request(app) + .get(`/api/v1/rating/${createdArticle.dataValues.slug}/article`) + .end((err, res) => { + expect(res).to.have.status(status.OK); + res.body.should.be.an('object'); + done(); + }); + }); + it('sort articles by rating', (done) => { + chai + .request(app) + .get('/api/v1/rating/articles?limit=1&offset=0') + .end((err, res) => { + console.log('hano', res.body); + // expect(res).to.have.status(status.OK); + // res.body.should.be.an('object'); + done(); + }); + }); + it("sort article's rating", (done) => { + chai + .request(app) + .get(`/api/v1/rating/${createdArticle.dataValues.slug}/articles?limit=1&offset=0`) + .end((err, res) => { + expect(res).to.have.status(status.OK); + res.body.should.be.an('object'); + done(); + }); + }); +}); diff --git a/src/tests/controllers/TagController.test.js b/src/tests/controllers/TagController.test.js new file mode 100644 index 00000000..d38efc05 --- /dev/null +++ b/src/tests/controllers/TagController.test.js @@ -0,0 +1,114 @@ +import chai from 'chai'; +import chaiHttp from 'chai-http'; +import jwt from 'jsonwebtoken'; +import app from '../../app'; +import status from '../../config/status'; +import db from '../../models'; +import * as Factory from '../../helpers/factory'; + +const { expect } = chai; +chai.should(); + +const article = Factory.article.build(); +const user = Factory.user.build(); +chai.use(chaiHttp); +let accessToken = ''; +let createdUser = ''; +delete user.id; +user.email = 'rosie@haven.com'; +user.username = 'rosie123'; +describe('TAGS', () => { + let response = ''; + before(async () => { + createdUser = (await db.User.create(user, { logging: false })).dataValues; + article.userId = createdUser.id; + accessToken = jwt.sign( + { id: createdUser.id, role: createdUser.role, permissions: createdUser.permissions }, + process.env.SECRET_KEY, + { expiresIn: '1d' } + ); + delete article.id; + delete article.readTime; + article.slug = 'lorem-ipsum-foo-hasli234rhjav'; + article.userId = createdUser.id; + response = await db.Article.create(article, { logging: false }); + }); + it('should create a tag', (done) => { + chai + .request(app) + .put(`/api/v1/articles/${response.dataValues.slug}/tags`) + .send({ tagList: ['Holla'] }) + .set('access-token', accessToken) + .end((err, res) => { + expect(res).to.have.status(status.CREATED); + res.body.should.be.an('object'); + res.body.response.should.equal('Tag list has been updated'); + done(); + }); + }); + it('should not create tags if they are more than 5', (done) => { + chai + .request(app) + .put(`/api/v1/articles/${response.dataValues.slug}/tags`) + .send({ tagList: ['Morning', 'Bonjour', 'Bonjourno', 'Dias', 'Hakuna Matata', 'Energy'] }) + .set('access-token', accessToken) + .end((err, res) => { + expect(res).to.have.status(status.BAD_REQUEST); + res.body.should.be.an('object'); + res.body.errors[0].should.equal('tagList must contain less than or equal to 5 items'); + done(); + }); + }); + it('should delete a tag', (done) => { + chai + .request(app) + .delete(`/api/v1/articles/${response.dataValues.slug}/tags`) + .send({ tagList: ['Holla'] }) + .set('access-token', accessToken) + .end((err, res) => { + expect(res).to.have.status(status.OK); + res.body.should.be.an('object'); + res.body.response.should.equal('Tag has been deleted'); + done(); + }); + }); + it('should get a list of all tags', (done) => { + chai + .request(app) + .get('/api/v1/tags') + .set('access-token', accessToken) + .end((err, res) => { + expect(res).to.have.status(status.OK); + res.body.should.be.an('object'); + res.body.tags.should.be.an('array'); + done(); + }); + }); + it('should not create tags if it is a string', (done) => { + const tagList = 'text'; + chai + .request(app) + .put(`/api/v1/articles/${response.dataValues.slug}/tags`) + .send({ tagList }) + .set('access-token', accessToken) + .end((err, res) => { + expect(res).to.have.status(status.BAD_REQUEST); + res.body.should.be.an('object'); + res.body.errors[0].should.equal('tagList must be an array'); + done(); + }); + }); + it('should not create tags if tagList is not provided', (done) => { + chai + .request(app) + .put(`/api/v1/articles/${response.dataValues.slug}/tags`) + .send() + .set('access-token', accessToken) + .end((err, res) => { + expect(res).to.have.status(status.BAD_REQUEST); + res.body.should.be.an('object'); + res.body.errors[0].should.equal('tagList is required'); + done(); + }); + }); +}); diff --git a/src/tests/controllers/UploadController.test.js b/src/tests/controllers/UploadController.test.js new file mode 100644 index 00000000..7f72fb99 --- /dev/null +++ b/src/tests/controllers/UploadController.test.js @@ -0,0 +1,71 @@ +import chai from 'chai'; +import chaiHttp from 'chai-http'; +import jwt from 'jsonwebtoken'; +import path from 'path'; +import express from 'express'; +import cloudinary from 'cloudinary'; +import app from '../../app'; +import status from '../../config/status'; +import db from '../../models'; +import * as Factory from '../../helpers/factory'; +import UploadController from '../../controllers/UploadController'; + +const { expect } = chai; +chai.should(); + +const appLocal = express(); +const router = express.Router(); + +appLocal.use(express.json()); +appLocal.use( + express.urlencoded({ + extended: false + }) +); + +appLocal.use('/api/v1/upload', router.post('/', UploadController.save)); + +const article = Factory.article.build(); +delete article.id; + +const user = Factory.user.build(); +chai.use(chaiHttp); +let accessToken = ''; +let createdUser = ''; +delete user.id; +user.email = 'multer@haven.com'; +user.username = 'multer123'; +describe('UPLOAD', () => { + before(async () => { + createdUser = (await db.User.create(user, { logging: false })).dataValues; + accessToken = jwt.sign( + { id: createdUser.id, role: createdUser.role, permissions: createdUser.permissions }, + process.env.SECRET_KEY, + { expiresIn: '1d' } + ); + }); + it('should get error if no gallery found', (done) => { + chai + .request(app) + .get('/api/v1/gallery') + .set('access-token', accessToken) + .end((err, res) => { + expect(res).to.have.status(status.NOT_FOUND); + res.body.should.be.an('object'); + res.body.errors.should.be.an('object'); + res.body.errors.gallery.should.be.an('string'); + res.body.errors.gallery.should.equal("You don't have image gallery yet"); + done(); + }); + }); + it('Should not upload image if no image selected', (done) => { + chai + .request(appLocal) + .post('/api/v1/upload') + .set('access-token', accessToken) + .end((err, res) => { + res.status.should.equal(status.BAD_REQUEST); + done(); + }); + }); +}); diff --git a/src/tests/controllers/UserController.test.js b/src/tests/controllers/UserController.test.js new file mode 100644 index 00000000..27f23a59 --- /dev/null +++ b/src/tests/controllers/UserController.test.js @@ -0,0 +1,420 @@ +/* eslint-disable no-unused-expressions */ +/* eslint-disable import/no-extraneous-dependencies */ +import chai from 'chai'; +import chaiHttp from 'chai-http'; +import jwt from 'jsonwebtoken'; +import app from '../../app'; +import status from '../../config/status'; +import db from '../../models'; +import * as Factory from '../../helpers/factory'; + +chai.should(); +chai.use(chaiHttp); +const user = Factory.user.build(); + +let userToken = ''; +let accessToken = ''; +const fakeToken = jwt.sign( + { + id: {}, + role: user.role, + permissions: user.permissions + }, + process.env.SECRET_KEY, + { expiresIn: '1d' } +); +// user test +let theNewUser; +let createdUser; +describe('user tests', () => { + // test signup; + before(async () => { + try { + await db.User.destroy({ + truncate: true, + cascade: true, + logging: false + }); + theNewUser = { + firstName: 'prince', + lastName: 'sengayireman', + username: 'daprinceHero', + email: 'psengayire119@gmail.com', + password: 'Prince@1234', + role: 'admin', + isActive: true + }; + createdUser = (await db.User.create(theNewUser, { logging: false })).get(); + accessToken = jwt.sign( + { + id: createdUser.id, + role: createdUser.role, + permissions: user.permissions + }, + process.env.SECRET_KEY, + { expiresIn: '1d' } + ); + } catch (error) { + throw error; + } + }); + describe('user signup', () => { + delete user.id; + it('Should not register new user when pass role and permission', (done) => { + chai + .request(app) + .post('/api/v1/auth/signup') + .send(user) + .end((err, res) => { + res.body.should.be.an('object'); + res.status.should.be.equal(status.UNAUTHORIZED); + done(); + }); + }); + it('Should register new user', (done) => { + delete user.role; + delete user.permissions; + chai + .request(app) + .post('/api/v1/auth/signup') + .send(user) + .end((err, res) => { + res.body.should.be.an('object'); + done(); + }); + }); + it('chatch error', (done) => { + chai + .request(app) + .post('/api/v1/auth/signup') + .send('...') + .end((err, res) => { + res.status.should.be.equal(status.BAD_REQUEST); + done(); + }); + }); + it('Should fail to register new user if exists', (done) => { + chai + .request(app) + .post('/api/v1/auth/signup') + .send(user) + .end((err, res) => { + res.body.should.be.an('object'); + res.status.should.be.equal(status.EXIST); + res.body.should.have.property('errors'); + done(); + }); + }); + it('Should fail to register new user if any input missing', (done) => { + chai + .request(app) + .post('/api/v1/auth/signup') + .send({ + firstName: '', + lastName: '', + username: '', + email: '', + password: '' + }) + .end((err, res) => { + res.body.should.be.an('object'); + res.status.should.be.equal(status.BAD_REQUEST); + done(); + }); + }); + }); + // test sign in + describe('user sign in', () => { + const userSignIn = { + email: user.email, + password: user.password, + isActive: true + }; + it('should activate user account', (done) => { + const userAccessToken = jwt.sign( + { + email: user.email, + expiresIn: '2h' + }, + process.env.SECRET_KEY, + { expiresIn: '1d' } + ); + chai + .request(app) + .get(`/api/v1/auth/activate/${userAccessToken}`) + .set('access-token', userAccessToken) + .end((err, res) => { + chai.expect(res).to.redirect; + done(); + }); + }); + // test user sign in + it('sign in user', (done) => { + chai + .request(app) + .post('/api/v1/auth/login') + .send(userSignIn) + .end((err, res) => { + userToken = res.body.token; + res.body.should.be.an('object'); + res.status.should.be.equal(status.OK); + res.body.should.have.property('token'); + done(); + }); + }); + it(' should fail if password not match', (done) => { + chai + .request(app) + .post('/api/v1/auth/login') + .send({ + email: user.email, + password: '12313223123' + }) + .end((err, res) => { + res.status.should.be.equal(status.UNAUTHORIZED); + done(); + }); + }); + }); + + it(' should fail if password is not valid', (done) => { + chai + .request(app) + .post('/api/v1/auth/login') + .send({ + email: user.email, + password: '' + }) + .end((err, res) => { + res.status.should.be.equal(status.BAD_REQUEST); + done(); + }); + }); + + it(' should fail if email is not valid', (done) => { + chai + .request(app) + .post('/api/v1/auth/login') + .send({ + email: 'user.email', + password: 'prince' + }) + .end((err, res) => { + res.status.should.be.equal(status.UNAUTHORIZED); + done(); + }); + }); + describe('admin test', () => { + const Adminuser = Factory.user.build(); + it('admin can create user', (done) => { + Adminuser.permissions = JSON.parse(Adminuser.permissions); + Adminuser.username = 'prince'; + Adminuser.email = 'princesengayire@andela.com'; + delete Adminuser.id; + chai + .request(app) + .post('/api/v1/users/') + .set('access-token', accessToken) + .send(Adminuser) + .end((err, res) => { + res.body.should.be.an('object'); + res.status.should.be.equal(status.CREATED); + done(); + }); + }); + + it('should not create user when exists', (done) => { + chai + .request(app) + .post('/api/v1/users/') + .set('access-token', accessToken) + .send(Adminuser) + .end((err, res) => { + res.body.should.be.an('object'); + res.status.should.be.equal(status.EXIST); + done(); + }); + }); + + it('should not create user if all filed are not there', (done) => { + delete Adminuser.firstName; + delete Adminuser.role; + delete Adminuser.permissions; + chai + .request(app) + .post('/api/v1/users/') + .set('access-token', accessToken) + .send(Adminuser.lastName) + .end((err, res) => { + res.body.should.be.an('object'); + res.status.should.be.equal(status.BAD_REQUEST); + done(); + }); + }); + it('should delete user', async () => { + const newUser = { + firstName: 'prince', + lastName: 'sengayire', + username: 'daprince333', + email: 'kagaramag1200gg@gmail.com', + password: 'Prince@1234', + isActive: true, + role: 'admin', + permissions: + '{"articles":["read","delete"],"comments":["read","delete"],"tags":["read","create","delete"],"users":["read","create","edit","delete"],"permissions":["read","create","edit","delete"]}' + }; + const createUser = await db.User.create(newUser, { logging: false }); + const { id } = createUser.dataValues; + chai + .request(app) + .delete(`/api/v1/users/${id}`) + .set('access-token', accessToken) + .end((err, res) => { + res.status.should.be.equal(status.OK); + }); + }); + + // follow user + it('follow user', (done) => { + const { username } = createdUser; + chai + .request(app) + .patch(`/api/v1/users/${username}/follow`) + .set('access-token', userToken) + .end((err, res) => { + res.status.should.be.equal(status.CREATED); + done(); + }); + }); + it('should not follow user when the follower id is not valid', (done) => { + const { username } = createdUser; + chai + .request(app) + .patch(`/api/v1/users/${username}/unfollow`) + .set('access-token', fakeToken) + .end((err, res) => { + res.status.should.be.equal(status.SERVER_ERROR); + done(); + }); + }); + it('fail to follow user when already following that user', (done) => { + const { username } = createdUser; + chai + .request(app) + .patch(`/api/v1/users/${username}/follow`) + .set('access-token', userToken) + .end((err, res) => { + res.status.should.be.equal(status.EXIST); + done(); + }); + }); + + it('should not allow to follow your self', (done) => { + const { username } = user; + chai + .request(app) + .patch(`/api/v1/users/${username}/follow`) + .set('access-token', userToken) + .end((err, res) => { + res.status.should.be.equal(status.BAD_REQUEST); + done(); + }); + }); + it("should not get user's followers", (done) => { + chai + .request(app) + .get('/api/v1/users/followers') + .set('access-token', userToken) + .end((err, res) => { + res.status.should.be.equal(status.NOT_FOUND); + done(); + }); + }); + it("get user's followers", (done) => { + const Token = jwt.sign( + { + id: createdUser.id, + role: createdUser.role, + permissions: createdUser.permissions + }, + process.env.SECRET_KEY, + { expiresIn: '1d' } + ); + chai + .request(app) + .get('/api/v1/users/followers') + .set('access-token', Token) + .end((err, res) => { + res.status.should.be.equal(status.OK); + done(); + }); + }); + it("get all user's following ", (done) => { + chai + .request(app) + .get('/api/v1/users/following') + .set('access-token', userToken) + .end((err, res) => { + res.status.should.be.equal(status.OK); + done(); + }); + }); + it('should not return followers', (done) => { + chai + .request(app) + .get('/api/v1/users/following') + .set('access-token', accessToken) + .end((err, res) => { + res.status.should.be.equal(status.NOT_FOUND); + done(); + }); + }); + it('unfollow user ', (done) => { + const { username } = createdUser; + chai + .request(app) + .patch(`/api/v1/users/${username}/unfollow`) + .set('access-token', userToken) + .end((err, res) => { + res.status.should.be.equal(status.OK); + done(); + }); + }); + + it('should not unfollow user when not found', (done) => { + const { username } = 'princho'; + chai + .request(app) + .patch(`/api/v1/users/${username}/unfollow`) + .set('access-token', userToken) + .end((err, res) => { + res.status.should.be.equal(status.BAD_REQUEST); + done(); + }); + }); + + it('should not unfollow user when not following', (done) => { + const { username } = Adminuser; + chai + .request(app) + .patch(`/api/v1/users/${username}/unfollow`) + .set('access-token', userToken) + .end((err, res) => { + res.status.should.be.equal(status.BAD_REQUEST); + done(); + }); + }); + + it('return all users', (done) => { + chai + .request(app) + .get('/api/v1/users?limit=1&offset=0') + .set('access-token', accessToken) + .end((err, res) => { + res.body.should.be.an('object'); + res.status.should.be.equal(status.OK); + done(); + }); + }); + }); +}); diff --git a/src/tests/controllers/index.js b/src/tests/controllers/index.js new file mode 100644 index 00000000..c96b0b9b --- /dev/null +++ b/src/tests/controllers/index.js @@ -0,0 +1,6 @@ +import './AuthPassportController.test'; +import './ArticleController.test'; +import './UserController.test'; +import './TagController.test'; +import './UploadController.test'; +import './RatingController.test'; diff --git a/src/tests/helpers/eventListener.test.js b/src/tests/helpers/eventListener.test.js new file mode 100644 index 00000000..4e151a6e --- /dev/null +++ b/src/tests/helpers/eventListener.test.js @@ -0,0 +1,13 @@ +/* eslint-disable no-unused-expressions */ +/* eslint-disable import/no-extraneous-dependencies */ +import chai from 'chai'; +import eventEmitter from '../../helpers/eventEmitter'; + +const { expect } = chai; + +describe('Event listener', () => { + it('should listen to an error event', () => { + const event = eventEmitter.emit('error', new Error('event error')); + expect(event).to.be.true; + }); +}); diff --git a/src/tests/helpers/generateReadTime.test.js b/src/tests/helpers/generateReadTime.test.js new file mode 100644 index 00000000..fde7850b --- /dev/null +++ b/src/tests/helpers/generateReadTime.test.js @@ -0,0 +1,23 @@ +// eslint-disable-next-line import/no-extraneous-dependencies +import chai from 'chai'; +// eslint-disable-next-line import/no-extraneous-dependencies +import chaiHttp from 'chai-http'; +import * as helpers from '../../helpers'; +import * as Factory from '../../helpers/factory'; + +// const { expect } = chai; +chai.should(); +const readtime = Factory.readtime.build(); +chai.use(chaiHttp); + +describe('Readtime', async () => { + it('should add 12 if there is one image in the content of article', async () => { + await helpers.generator.readtime(readtime.oneimage); + }); + it('should add 22 if there is two images in the content of article', async () => { + await helpers.generator.readtime(readtime.twoimage); + }); + it('should add 22 for the first 2 images and add 2 seconds for each additional image in the content of article', async () => { + await helpers.generator.readtime(readtime.threeimage); + }); +}); diff --git a/src/tests/helpers/index.js b/src/tests/helpers/index.js new file mode 100644 index 00000000..c7ad00df --- /dev/null +++ b/src/tests/helpers/index.js @@ -0,0 +1,6 @@ +import './validation'; +import './tokens'; +import './generateReadTime.test'; +import './notifications'; +import './eventListener.test'; +import './isActiveUser.test'; diff --git a/src/tests/helpers/isActiveUser.test.js b/src/tests/helpers/isActiveUser.test.js new file mode 100644 index 00000000..bdffa77f --- /dev/null +++ b/src/tests/helpers/isActiveUser.test.js @@ -0,0 +1,43 @@ +import chai from 'chai'; +import chaiHttp from 'chai-http'; +import db from '../../models'; +import * as Factory from '../../helpers/factory'; +import isActiveUser from '../../helpers/isActiveUser'; + +chai.should(); + +chai.use(chaiHttp); +const newUser = Factory.user.build(); +delete newUser.id; +let activeUser = ''; +let inactiveUser = ''; + +describe('test activated account', () => { + before(async () => { + try { + await db.User.destroy({ + truncate: true, + cascade: true, + logging: false + }); + newUser.isActive = true; + activeUser = (await db.User.create(newUser, { logging: false })).dataValues; + newUser.isActive = false; + newUser.email = 'prince@gmail.com'; + newUser.username = 'prince'; + inactiveUser = (await db.User.create(newUser, { logging: false })).dataValues; + } catch (error) { + throw error; + } + }); + it('return false when user does not exist', async () => { + await isActiveUser({ email: 'aaa@bbb.ccc' }); + }); + it('return false when user is not active', async () => { + await isActiveUser({ email: inactiveUser.email }); + }); + + it('return true when user is active', async () => { + await isActiveUser({ email: activeUser.email }); + }); +}); diff --git a/src/tests/helpers/notifications/commentArticle.test.js b/src/tests/helpers/notifications/commentArticle.test.js new file mode 100644 index 00000000..9022c868 --- /dev/null +++ b/src/tests/helpers/notifications/commentArticle.test.js @@ -0,0 +1,76 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import chai from 'chai'; +import dotenv from 'dotenv'; +import db from '../../../models'; +import * as Factory from '../../../helpers/factory'; +import commentArticle from '../../../helpers/notifications/commentArticle'; + +dotenv.config(); + +const { expect } = chai; + +let createdUserOne = ''; +let createdUserTwo = ''; +let createdArticle = ''; +let createdConfig = ''; +let createdFavorite = ''; +let createdComment = ''; + +const userOne = Factory.user.build(); +const userTwo = Factory.user.build(); +const article = Factory.article.build(); +const comment = Factory.comment.build(); +const notificationConfig = Factory.notificationConfig.build(); + +delete userOne.id; +delete userTwo.id; +delete article.id; +delete comment.id; + +userTwo.email = 'aaa@bbb.ccc'; +userTwo.username = 'aaabbb'; + +describe('Publish article notification', () => { + before(async () => { + try { + await db.User.destroy({ + truncate: true, + cascade: true, + logging: false + }); + + createdUserOne = (await db.User.create(userOne, { logging: false })).get(); + article.userId = createdUserOne.id; + createdArticle = (await db.Article.create(article, { logging: false })).get(); + + createdUserTwo = (await db.User.create(userTwo, { logging: false })).get(); + notificationConfig.userId = createdUserTwo.id; + notificationConfig.config = JSON.stringify(notificationConfig.config); + + createdConfig = (await db.NotificationConfig.create(notificationConfig, { + logging: false + })).get(); + + createdFavorite = (await db.FavoriteArticle.create( + { userId: createdUserTwo.id, articleSlug: createdArticle.slug }, + { logging: false } + )).get(); + + comment.userId = createdUserOne.id; + comment.articleSlug = createdArticle.slug; + createdComment = (await db.Comment.create(comment, { logging: false })).get(); + } catch (error) { + throw error; + } + }); + + it('should notify a user when an article they favorite has new comment', async () => { + const notifications = await commentArticle(createdComment); + expect(notifications).to.not.include.keys('errors'); + }); + + it('should not notify a user if no parameter is passed', async () => { + const notifications = await commentArticle(); + expect(notifications).to.include.keys('errors'); + }); +}); diff --git a/src/tests/helpers/notifications/index.js b/src/tests/helpers/notifications/index.js new file mode 100644 index 00000000..dab371b6 --- /dev/null +++ b/src/tests/helpers/notifications/index.js @@ -0,0 +1,2 @@ +import './publishArticle.test'; +import './commentArticle.test'; diff --git a/src/tests/helpers/notifications/publishArticle.test.js b/src/tests/helpers/notifications/publishArticle.test.js new file mode 100644 index 00000000..ef82e27c --- /dev/null +++ b/src/tests/helpers/notifications/publishArticle.test.js @@ -0,0 +1,68 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import chai from 'chai'; +import dotenv from 'dotenv'; +import db from '../../../models'; +import * as Factory from '../../../helpers/factory'; +import publishArticle from '../../../helpers/notifications/publishArticle'; + +dotenv.config(); + +const { expect } = chai; + +let createdUserOne = ''; +let createdUserTwo = ''; +let createdArticle = ''; +let createdConfig = ''; +let createdFollow = ''; + +const userOne = Factory.user.build(); +const userTwo = Factory.user.build(); +const article = Factory.article.build(); +const notificationConfig = Factory.notificationConfig.build(); + +delete userOne.id; +delete userTwo.id; +delete article.id; + +userTwo.email = 'aaa@bbb.ccc'; +userTwo.username = 'aaabbb'; + +describe('Publish article notification', () => { + before(async () => { + try { + await db.User.destroy({ + truncate: true, + cascade: true, + logging: false + }); + + createdUserOne = (await db.User.create(userOne, { logging: false })).get(); + article.userId = createdUserOne.id; + createdArticle = (await db.Article.create(article, { logging: false })).get(); + + createdUserTwo = (await db.User.create(userTwo, { logging: false })).get(); + notificationConfig.userId = createdUserTwo.id; + notificationConfig.config = JSON.stringify(notificationConfig.config); + + createdConfig = (await db.NotificationConfig.create(notificationConfig, { + logging: false + })).get(); + createdFollow = (await db.Follows.create( + { userId: createdUserTwo.id, followed: createdUserOne.id }, + { logging: false } + )).get(); + } catch (error) { + throw error; + } + }); + + it('should notify a user when an user they follow published an article', async () => { + const notifications = await publishArticle(createdUserOne.id, createdArticle.slug); + expect(notifications).to.not.include.keys('errors'); + }); + + it('should not notify a user if no parameter is passed', async () => { + const notifications = await publishArticle(); + expect(notifications).to.include.keys('errors'); + }); +}); diff --git a/src/tests/helpers/passportLocal.test.js b/src/tests/helpers/passportLocal.test.js new file mode 100644 index 00000000..e7149f5b --- /dev/null +++ b/src/tests/helpers/passportLocal.test.js @@ -0,0 +1,38 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import chai from 'chai'; +import chaiHttp from 'chai-http'; +import db from '../../models'; +import passport from '../../config/passportLocalConfig'; +import * as Factory from '../../helpers/factory'; + +chai.should(); + +chai.use(chaiHttp); +let user = {}; +const theUser = Factory.user.build(); +delete theUser.id; +describe('test local passport authenticate', () => { + before(async () => { + try { + await db.User.destroy({ where: { email: theUser.email } }); + user = await db.User.create(theUser); + theUser.id = user.dataValues.id; + } catch (error) { + return error; + } + }); + it('return authanticated user with JwtPayload', async () => { + const checkUser = await passport({ id: user.dataValues.id }, (err, user) => user); + chai.expect(Object.keys(checkUser).length).to.be.above(0); + }); + it('fail when provide wrong token', async () => { + const badId = '1000'; + const checkUser = await passport( + { + id: badId + }, + (err, user) => user + ); + chai.expect(Object.keys(checkUser).length).to.be.below(1); + }); +}); diff --git a/src/tests/helpers/tokens/index.js b/src/tests/helpers/tokens/index.js new file mode 100644 index 00000000..d97de53e --- /dev/null +++ b/src/tests/helpers/tokens/index.js @@ -0,0 +1,2 @@ +import './tokenGenerator.test'; +import './tokenDecoder.test'; diff --git a/src/tests/helpers/tokens/tokenDecoder.test.js b/src/tests/helpers/tokens/tokenDecoder.test.js new file mode 100644 index 00000000..292494b6 --- /dev/null +++ b/src/tests/helpers/tokens/tokenDecoder.test.js @@ -0,0 +1,28 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import chai from 'chai'; +import jwt from 'jsonwebtoken'; +import dotenv from 'dotenv'; +import tokenDecoder from '../../../helpers/tokens/tokenDecoder'; + +dotenv.config(); + +const { expect } = chai; + +const token = jwt.sign({ payload: 'my payload' }, process.env.SECRET_KEY, { expiresIn: '1d' }); + +describe('Token decodor', () => { + it('should return an object containing the decoded token', () => { + const decodedToken = tokenDecoder(token); + expect(decodedToken).to.not.include.keys('errors'); + }); + + it('should not return the decoded token if the token is not valid', () => { + const decodedToken = tokenDecoder('token'); + expect(decodedToken).to.include.keys('errors'); + }); + + it('should not return the decoded token if the token is not passed', () => { + const decodedToken = tokenDecoder(); + expect(decodedToken).to.include.keys('errors'); + }); +}); diff --git a/src/tests/helpers/tokens/tokenGenerator.test.js b/src/tests/helpers/tokens/tokenGenerator.test.js new file mode 100644 index 00000000..230042ca --- /dev/null +++ b/src/tests/helpers/tokens/tokenGenerator.test.js @@ -0,0 +1,27 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import chai from 'chai'; +import tokenGenerator from '../../../helpers/tokens/tokenGenerator'; + +const { expect } = chai; + +describe('Token generator', () => { + it('should return a token if the payload is specified', () => { + expect(tokenGenerator({ payload: 'payload' }).length).to.be.above(0); + }); + + it('should not return a token if the payload is null', () => { + expect(tokenGenerator(null)).to.be.equal(null); + }); + + it('should not return a token if the payload is a number', () => { + expect(tokenGenerator(1)).to.be.equal(null); + }); + + it('should not return a token if the payload is an empty object', () => { + expect(tokenGenerator({})).to.be.equal(null); + }); + + it('should not return a token if no parameter is passed', () => { + expect(tokenGenerator()).to.be.equal(null); + }); +}); diff --git a/src/tests/helpers/validation/articleSlug.test.js b/src/tests/helpers/validation/articleSlug.test.js new file mode 100644 index 00000000..0a63fb4e --- /dev/null +++ b/src/tests/helpers/validation/articleSlug.test.js @@ -0,0 +1,12 @@ +import chai from 'chai'; +import * as helpers from '../../../helpers'; + +chai.should(); + +describe('Article Slug helper', () => { + it('should pass', () => { + const slug = 'hello-world-good-things-to-come'; + const checkSlug = helpers.validation.articleSlug(slug); + chai.expect(checkSlug).to.be.an('object'); + }); +}); diff --git a/src/tests/helpers/validation/commentErrors.test.js b/src/tests/helpers/validation/commentErrors.test.js new file mode 100644 index 00000000..48c058ad --- /dev/null +++ b/src/tests/helpers/validation/commentErrors.test.js @@ -0,0 +1,14 @@ +import chai from 'chai'; +import commentErrors from '../../../helpers/validation/commentErrors'; + +describe('Comment helper validator', () => { + it('should pass', () => { + const errors = [{ type: 'any.empty' }, { type: 'string.min' }]; + chai.expect(commentErrors(errors).length).to.be.above(0); + }); + + it('should not pass', () => { + const errors = [{ type: 'aaaaa' }]; + chai.expect(commentErrors(errors).length).to.be.above(0); + }); +}); diff --git a/src/tests/helpers/validation/email.test.js b/src/tests/helpers/validation/email.test.js new file mode 100644 index 00000000..fda454e4 --- /dev/null +++ b/src/tests/helpers/validation/email.test.js @@ -0,0 +1,28 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import chai from 'chai'; +import validateEmail from '../../../helpers/validation/email'; + +const { expect } = chai; + +describe('Email validation', () => { + it('should return an array of error messages', () => { + expect(validateEmail('ab/c@gmail.com', 'required').length).to.be.above(0); + }); + + it('should return an array of error messages', () => { + expect(validateEmail('abc@gma/il.com', 'required').length).to.be.above(0); + }); + + it('should return an array of error messages', () => { + expect(validateEmail('abc@gmail.co/m', 'required').length).to.be.above(0); + }); + + it('should return an empty array if there is no error', () => { + expect(validateEmail('abcd@gmail.com', 'required').length).to.be.equals(0); + expect(validateEmail('', false).length).to.be.equals(0); + }); + + it('should return an empty array if no parameter', () => { + expect(validateEmail().length).to.be.equals(0); + }); +}); diff --git a/src/tests/helpers/validation/index.js b/src/tests/helpers/validation/index.js new file mode 100644 index 00000000..874e6688 --- /dev/null +++ b/src/tests/helpers/validation/index.js @@ -0,0 +1,4 @@ +import './commentErrors.test'; +import './email.test'; +import './password.test'; +import './articleSlug.test'; diff --git a/src/tests/helpers/validation/password.test.js b/src/tests/helpers/validation/password.test.js new file mode 100644 index 00000000..3edbbb19 --- /dev/null +++ b/src/tests/helpers/validation/password.test.js @@ -0,0 +1,36 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import chai from 'chai'; +import validatePassword from '../../../helpers/validation/password'; + +const { expect } = chai; + +describe('Password validation', () => { + it('should return an array of error messages', () => { + expect(validatePassword('abcde12345', 'required').length).to.be.above(0); + }); + + it('should return an array of error messages', () => { + expect(validatePassword('abcd', 'required').length).to.be.above(0); + }); + + it('should return an array of error messages', () => { + expect(validatePassword('abcdefgh', 'required').length).to.be.above(0); + }); + + it('should return an array of error messages', () => { + expect(validatePassword('12345678', 'required').length).to.be.above(0); + }); + + it('should return an array of error messages', () => { + expect(validatePassword('Abcde12345', 'required').length).to.be.above(0); + }); + + it('should return an empty array if there is no error', () => { + expect(validatePassword('Abcde12345!', 'required').length).to.be.equals(0); + expect(validatePassword('', false).length).to.be.equals(0); + }); + + it('should return an empty array if no parameter', () => { + expect(validatePassword().length).to.be.equals(0); + }); +}); diff --git a/src/tests/index.js b/src/tests/index.js new file mode 100644 index 00000000..049c6db3 --- /dev/null +++ b/src/tests/index.js @@ -0,0 +1,8 @@ +import './config'; +import './models'; +import './queries'; +import './helpers'; +import './middlewares'; +import './controllers'; +import './routes'; +import './routes/404.test'; diff --git a/src/tests/middlewares/asyncHandler.test.js b/src/tests/middlewares/asyncHandler.test.js new file mode 100644 index 00000000..80efd24d --- /dev/null +++ b/src/tests/middlewares/asyncHandler.test.js @@ -0,0 +1,41 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import chai from 'chai'; +import chaiHttp from 'chai-http'; +import express from 'express'; +import status from '../../config/status'; +import * as Factory from '../../helpers/factory'; +import asyncHandler from '../../middlewares/asyncHandler'; + +chai.use(chaiHttp); + +const app = express(); +const router = express.Router(); + +app.use(express.json()); +app.use( + express.urlencoded({ + extended: false + }) +); + +app.use('/api/v1/asyncHandler/', router.get('/', asyncHandler('error'))); + +const newUser = Factory.user.build(); +const newArticle = Factory.article.build(); +const newComment = Factory.comment.build(); + +delete newUser.id; +delete newArticle.id; +delete newComment.id; + +describe('MIDDLEWARE : Test the asyncHandler middleware', () => { + it('Should check the error', (done) => { + chai + .request(app) + .get('/api/v1/asyncHandler') + .end((err, res) => { + res.should.have.status(status.SERVER_ERROR); + done(); + }); + }); +}); diff --git a/src/tests/middlewares/checkPermission.test.js b/src/tests/middlewares/checkPermission.test.js new file mode 100644 index 00000000..3ead4cd9 --- /dev/null +++ b/src/tests/middlewares/checkPermission.test.js @@ -0,0 +1,115 @@ +/* eslint-disable import/no-extraneous-dependencies */ +/* eslint-disable import/no-extraneous-dependencies */ +import chai from 'chai'; +import chaiHttp from 'chai-http'; +import express from 'express'; +import jwt from 'jsonwebtoken'; +import dotenv from 'dotenv'; +import db from '../../models'; +import status from '../../config/status'; +import * as Factory from '../../helpers/factory'; +import verifyToken from '../../middlewares/verifyToken'; +import checkPermission from '../../middlewares/checkPermissions'; + +dotenv.config(); + +const { expect } = chai; + +chai.use(chaiHttp); + +const app = express(); +const router = express.Router(); +let createdUser = ''; +const createdUserTwo = ''; +let token = ''; +let tokenTwo = ''; +const user = Factory.user.build(); +delete user.id; + +app.use(express.json()); +app.use( + express.urlencoded({ + extended: false + }) +); +app.use( + '/api/v1', + router.put('/users', verifyToken, checkPermission({ route: 'users', action: 'create' })) +); + +app.use( + '/api/v1', + router.put('/articles', verifyToken, checkPermission({ route: 'articles', action: 'edit' })) +); + +const newUser = Factory.user.build(); +delete newUser.id; + +describe('Verify permission', () => { + before(async () => { + try { + await db.User.destroy({ + truncate: true, + cascade: true, + logging: false + }); + createdUser = (await db.User.create(user, { logging: false })).dataValues; + token = jwt.sign( + { id: createdUser.id, role: createdUser.role, permissions: user.permissions }, + process.env.SECRET_KEY, + { + expiresIn: '1d' + } + ); + const permissions = JSON.parse(user.permissions); + permissions.articles = ['read']; + tokenTwo = jwt.sign( + { id: createdUser.id, role: createdUser.role, permissions: JSON.stringify(permissions) }, + process.env.SECRET_KEY, + { + expiresIn: '1d' + } + ); + } catch (error) { + throw error; + } + }); + + it('should not restrict the access', (done) => { + chai + .request(app) + .put('/api/v1/users') + .set('access-token', token) + .end((err, res) => { + expect(res.body).to.not.include.keys('errors'); + done(); + }); + }); + + it('should return a bad request', (done) => { + const invalidUserAccessToken = jwt.sign( + { id: createdUser.id, role: createdUser.role, permissions: '{}' }, + process.env.SECRET_KEY, + { expiresIn: '1d' } + ); + chai + .request(app) + .put('/api/v1/users') + .set('access-token', invalidUserAccessToken) + .end((err, res) => { + expect(res.status).to.equal(status.BAD_REQUEST); + done(); + }); + }); + + it('should return unauthorized on permission', (done) => { + chai + .request(app) + .put('/api/v1/articles') + .set('access-token', tokenTwo) + .end((err, res) => { + expect(res.status).to.equal(status.UNAUTHORIZED); + done(); + }); + }); +}); diff --git a/src/tests/middlewares/index.js b/src/tests/middlewares/index.js new file mode 100644 index 00000000..41789209 --- /dev/null +++ b/src/tests/middlewares/index.js @@ -0,0 +1,7 @@ +import './verifyToken.test'; +import './logout.test'; +import './asyncHandler.test'; +import './validateUser.test'; +import './verifyAdmin.test'; +import './checkPermission.test'; +import './isValidUser.test'; diff --git a/src/tests/middlewares/isValidUser.test.js b/src/tests/middlewares/isValidUser.test.js new file mode 100644 index 00000000..d55929a9 --- /dev/null +++ b/src/tests/middlewares/isValidUser.test.js @@ -0,0 +1,81 @@ +import chai from 'chai'; +import chaiHttp from 'chai-http'; +import express from 'express'; +import db from '../../models'; +import status from '../../config/status'; +import * as Factory from '../../helpers/factory'; +import isActiveUser from '../../middlewares/isActiveUser'; + +const { expect } = chai; + +chai.use(chaiHttp); + +const app = express(); +const router = express.Router(); + +app.use(express.json()); +app.use( + express.urlencoded({ + extended: false + }) +); + +app.use('/api/v1/auth/login', router.post('/', isActiveUser)); + +const newUser = Factory.user.build(); +delete newUser.id; +let activeUser = ''; +let inactiveUser = ''; + +describe('Test activated account', () => { + before(async () => { + try { + await db.User.destroy({ + truncate: true, + cascade: true, + logging: false + }); + newUser.isActive = true; + activeUser = (await db.User.create(newUser, { logging: false })).dataValues; + newUser.isActive = false; + newUser.email = 'prince@gmail.com'; + newUser.username = 'prince'; + inactiveUser = (await db.User.create(newUser, { logging: false })).dataValues; + } catch (error) { + throw error; + } + }); + + it('should return true when user is active', (done) => { + chai + .request(app) + .post('/api/v1/auth/login') + .send({ email: activeUser.email }) + .end((err, res) => { + expect(res.status).to.not.equal(status.UNAUTHORIZED); + done(); + }); + }); + + it('should return false when user is not active', (done) => { + chai + .request(app) + .post('/api/v1/auth/login') + .send({ email: inactiveUser.email }) + .end((err, res) => { + expect(res.status).to.equal(status.UNAUTHORIZED); + done(); + }); + }); + + it('should return false if the user does not exist', (done) => { + chai + .request(app) + .post('/api/v1/auth/login') + .send({ email: 'aaa@bbb.ccc' }) + .end((err, res) => { + expect(res.status).to.equal(status.UNAUTHORIZED); + done(); + }); + }); +}); diff --git a/src/tests/middlewares/logout.test.js b/src/tests/middlewares/logout.test.js new file mode 100644 index 00000000..5b7c605f --- /dev/null +++ b/src/tests/middlewares/logout.test.js @@ -0,0 +1,103 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import chai from 'chai'; +import chaiHttp from 'chai-http'; +import express from 'express'; +import jwt from 'jsonwebtoken'; +import dotenv from 'dotenv'; +import db from '../../models'; +import * as Factory from '../../helpers/factory'; +import verifyToken from '../../middlewares/verifyToken'; +import logout from '../../middlewares/logout'; + +dotenv.config(); + +const { expect } = chai; + +chai.use(chaiHttp); + +const app = express(); +const router = express.Router(); +let createdUser = ''; +let token = ''; +const user = Factory.user.build(); +delete user.id; + +app.use(express.json()); +app.use( + express.urlencoded({ + extended: false + }) +); + +app.use('/api/auth/logout', router.get('/', verifyToken, logout)); +app.use('/api/auth/logout', router.get('/:token', verifyToken, logout)); + +describe('Logout', () => { + before(async () => { + try { + await db.User.destroy({ + truncate: true, + cascade: true, + logging: false + }); + createdUser = (await db.User.create(user, { logging: false })).dataValues; + token = jwt.sign({ id: createdUser.id, role: createdUser.role }, process.env.SECRET_KEY, { + expiresIn: '1d' + }); + } catch (error) { + throw error; + } + }); + + it('should logout the user and blacklist the token', (done) => { + chai + .request(app) + .get('/api/auth/logout') + .set('access-token', token) + .end((err, res) => { + expect(res.body).to.not.include.keys('errors'); + done(); + }); + }); + + it('should not logout the user if the user does not exist', (done) => { + const token2 = jwt.sign({ id: 0, role: createdUser.role }, process.env.SECRET_KEY, { + expiresIn: '1d' + }); + chai + .request(app) + .get('/api/auth/logout') + .set('access-token', token2) + .end((err, res) => { + expect(res.body).to.include.keys('errors'); + done(); + }); + }); + + it('should not logout the user if the user does not exist', (done) => { + const token2 = jwt.sign({ id: {}, role: createdUser.role }, process.env.SECRET_KEY, { + expiresIn: '1d' + }); + chai + .request(app) + .get('/api/auth/logout') + .set('access-token', token2) + .end((err, res) => { + expect(res.body).to.include.keys('errors'); + done(); + }); + }); + + it('should not logout the user if the user does not exist', (done) => { + const token2 = jwt.sign({ id: {}, role: createdUser.role }, process.env.SECRET_KEY, { + expiresIn: '1d' + }); + chai + .request(app) + .get(`/api/auth/logout/${token2}`) + .end((err, res) => { + expect(res.body).to.include.keys('errors'); + done(); + }); + }); +}); diff --git a/src/tests/middlewares/validateUser.test.js b/src/tests/middlewares/validateUser.test.js new file mode 100644 index 00000000..cdb18c7a --- /dev/null +++ b/src/tests/middlewares/validateUser.test.js @@ -0,0 +1,137 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import chai from 'chai'; +import chaiHttp from 'chai-http'; +import express from 'express'; +import status from '../../config/status'; +import * as Factory from '../../helpers/factory'; +import validateUser from '../../middlewares/validateUser'; + +const { expect } = chai; + +chai.use(chaiHttp); + +const app = express(); +const router = express.Router(); + +app.use(express.json()); +app.use( + express.urlencoded({ + extended: false + }) +); + +app.use('/api/v1/users', router.post('/', validateUser)); +app.use('/api/v1/users', router.put('/', validateUser)); + +const newUser = Factory.user.build(); +delete newUser.id; +delete newUser.role; + +describe('Validate user inputs on registration', () => { + it('should return an error message if the body is empty', (done) => { + chai + .request(app) + .post('/api/v1/users') + .send({}) + .end((err, res) => { + const { errors } = res.body; + expect(res.status).to.equal(400); + expect(Object.keys(errors).length).to.be.above(0); + done(); + }); + }); + + it('should return an error message if the email is not valid', (done) => { + newUser.email = 'abcdz@gma/il.com'; + chai + .request(app) + .post('/api/v1/users') + .send(newUser) + .end((err, res) => { + const { errors } = res.body; + expect(res.status).to.equal(400); + expect(errors.email.length).to.be.above(0); + done(); + }); + }); + + it("should return an error message if the password doesn't meet the requirements", (done) => { + newUser.password = 'baaa1234'; + newUser.email = Factory.user.build().email; + chai + .request(app) + .post('/api/v1/users') + .send(newUser) + .end((err, res) => { + const { errors } = res.body; + expect(res.status).to.equal(400); + expect(errors.password.length).to.be.above(0); + done(); + }); + }); + + it('should not return any error', (done) => { + newUser.email = 'aaabbbccc@gmail.com'; + newUser.password = 'Baaa1234!'; + chai + .request(app) + .post('/api/v1/users') + .send(newUser) + .end((err, res) => { + expect(res.status).to.not.equal(400); + done(); + }); + }); + + it('should not return any error', (done) => { + newUser.email = ''; + newUser.password = 'Baaa1234!'; + chai + .request(app) + .post('/api/v1/users') + .send(newUser) + .end((err, res) => { + expect(res.status).to.equal(400); + done(); + }); + }); +}); + +describe('Validate user inputs when users want to update their profile', () => { + it('should not update the email if it is not valid', (done) => { + chai + .request(app) + .put('/api/v1/users') + .send({ email: 'aaa@bbb' }) + .end((err, res) => { + const { errors } = res.body; + expect(res.status).to.equal(status.BAD_REQUEST); + expect(errors.email.length).to.be.above(0); + done(); + }); + }); + + it("should not update the password if it doesn't meet the requirements", (done) => { + chai + .request(app) + .put('/api/v1/users') + .send({ password: 'baaa1234' }) + .end((err, res) => { + const { errors } = res.body; + expect(res.status).to.equal(status.BAD_REQUEST); + expect(errors.password.length).to.be.above(0); + done(); + }); + }); + + it('should not update user profile if some inputs are empty', (done) => { + chai + .request(app) + .put('/api/v1/users') + .send({ username: '' }) + .end((err, res) => { + expect(res.status).to.equal(status.BAD_REQUEST); + done(); + }); + }); +}); diff --git a/src/tests/middlewares/verifyAdmin.test.js b/src/tests/middlewares/verifyAdmin.test.js new file mode 100644 index 00000000..0696dcad --- /dev/null +++ b/src/tests/middlewares/verifyAdmin.test.js @@ -0,0 +1,81 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import chai from 'chai'; +import chaiHttp from 'chai-http'; +import express from 'express'; +import jwt from 'jsonwebtoken'; +import dotenv from 'dotenv'; +import db from '../../models'; +import status from '../../config/status'; +import * as Factory from '../../helpers/factory'; +import verifyToken from '../../middlewares/verifyToken'; +import verifyAdminUser from '../../middlewares/verifyAdmin'; + +dotenv.config(); + +const { expect } = chai; + +chai.use(chaiHttp); + +const app = express(); +const router = express.Router(); +let createdUser = ''; +let token = ''; +const user = Factory.user.build(); +delete user.id; + +app.use(express.json()); +app.use( + express.urlencoded({ + extended: false + }) +); + +app.use('/api/users/roles', router.get('/', verifyToken, verifyAdminUser)); + +const newUser = Factory.user.build(); +delete newUser.id; + +describe('Verify admin', () => { + before(async () => { + try { + await db.User.destroy({ + truncate: true, + cascade: true, + logging: false + }); + createdUser = (await db.User.create(user, { logging: false })).dataValues; + token = jwt.sign({ id: createdUser.id, role: createdUser.role }, process.env.SECRET_KEY, { + expiresIn: '1d' + }); + } catch (error) { + throw error; + } + }); + + it('should return a permission ', (done) => { + chai + .request(app) + .get('/api/users/roles') + .set('access-token', token) + .end((err, res) => { + expect(res.body).to.not.include.keys('errors'); + done(); + }); + }); + + it('should return a permission denied status code if the user is not an admin', (done) => { + const invalidUserAccessToken = jwt.sign( + { id: 0, role: createdUser.role, permissions: createdUser.permissions }, + process.env.SECRET_KEY, + { expiresIn: '1d' } + ); + chai + .request(app) + .get('/api/users/roles') + .set('access-token', invalidUserAccessToken) + .end((err, res) => { + expect(res.status).to.equal(status.ACCESS_DENIED); + done(); + }); + }); +}); diff --git a/src/tests/middlewares/verifyToken.test.js b/src/tests/middlewares/verifyToken.test.js new file mode 100644 index 00000000..e9458b44 --- /dev/null +++ b/src/tests/middlewares/verifyToken.test.js @@ -0,0 +1,108 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import chai from 'chai'; +import chaiHttp from 'chai-http'; +import express from 'express'; +import jwt from 'jsonwebtoken'; +import dotenv from 'dotenv'; +import db from '../../models'; +import status from '../../config/status'; +import * as Factory from '../../helpers/factory'; +import verifyToken from '../../middlewares/verifyToken'; + +dotenv.config(); + +const { expect } = chai; + +chai.use(chaiHttp); + +const app = express(); +const router = express.Router(); +let createdUser = ''; +let token = ''; +const user = Factory.user.build(); +delete user.id; + +app.use(express.json()); +app.use( + express.urlencoded({ + extended: false + }) +); + +app.use('/api/users', router.get('/', verifyToken)); +app.use('/api/users', router.get('/:token', verifyToken)); + +describe('Verify token', () => { + before(async () => { + try { + await db.User.destroy({ + truncate: true, + cascade: true, + logging: false + }); + createdUser = (await db.User.create(user, { logging: false })).dataValues; + token = jwt.sign({ id: createdUser.id, role: createdUser.role }, process.env.SECRET_KEY, { + expiresIn: '1d' + }); + await db.Token.create({ token, userId: createdUser.id }, { logging: false }); + } catch (error) { + throw error; + } + }); + + it('should return the decoded token is valid', (done) => { + chai + .request(app) + .get(`/api/users/${Factory.token.build().token}`) + .end((err, res) => { + expect(res.body).to.not.include.keys('errors'); + done(); + }); + }); + + it('should return the decoded token is valid', (done) => { + chai + .request(app) + .get('/api/users') + .set('access-token', Factory.token.build().token) + .end((err, res) => { + expect(res.body).to.not.include.keys('errors'); + done(); + }); + }); + + it('should return an unauthorized status code if the token is blacklisted', (done) => { + chai + .request(app) + .get('/api/users') + .set('access-token', token) + .end((err, res) => { + expect(res.status).to.equal(status.UNAUTHORIZED); + expect(res.body).to.include.keys('errors'); + done(); + }); + }); + + it('should return an unauthorized status code if the token is not provided', (done) => { + chai + .request(app) + .get('/api/users') + .end((err, res) => { + expect(res.status).to.equal(status.UNAUTHORIZED); + expect(res.body).to.include.keys('errors'); + done(); + }); + }); + + it('should return an unauthorized status code the token is not valid', (done) => { + chai + .request(app) + .get('/api/users') + .set('access-token', 'invalid-token') + .end((err, res) => { + expect(res.status).to.equal(status.UNAUTHORIZED); + expect(res.body).to.include.keys('errors'); + done(); + }); + }); +}); diff --git a/src/tests/models/articleBookmarkModel.test.js b/src/tests/models/articleBookmarkModel.test.js new file mode 100644 index 00000000..287161d9 --- /dev/null +++ b/src/tests/models/articleBookmarkModel.test.js @@ -0,0 +1,37 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import chai from 'chai'; +import sinonChai from 'sinon-chai'; + +import { + sequelize, dataTypes, checkModelName, checkPropertyExists +} from 'sequelize-test-helpers'; + +import ArticleBookmarkModel from '../../models/articlebookmark'; + +const { expect } = chai; +chai.use(sinonChai); + +// test articlebookmark model +describe('src/models/ArticleBookmark', () => { + const ArticleBookmark = ArticleBookmarkModel(sequelize, dataTypes); + const articlebookmark = new ArticleBookmark(); + + checkModelName(ArticleBookmark)('ArticleBookmark'); + + context('properties', () => { + ['userId', 'articleSlug', 'createdAt', 'updatedAt'].forEach( + checkPropertyExists(articlebookmark) + ); + }); + + context('check associations', () => { + const User = 'user'; + before(() => { + ArticleBookmark.associate({ User }); + }); + + it('defined a belongsTo association with User', () => { + expect(ArticleBookmark.belongsTo).to.have.been.calledWith(User); + }); + }); +}); diff --git a/src/tests/models/articleModel.test.js b/src/tests/models/articleModel.test.js new file mode 100644 index 00000000..58d46079 --- /dev/null +++ b/src/tests/models/articleModel.test.js @@ -0,0 +1,47 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import chai from 'chai'; +import sinonChai from 'sinon-chai'; + +import { + sequelize, dataTypes, checkModelName, checkPropertyExists +} from 'sequelize-test-helpers'; + +import ArticleModel from '../../models/article'; + +const { expect } = chai; +chai.use(sinonChai); + +// test article model +describe('src/models/Article', () => { + const Article = ArticleModel(sequelize, dataTypes); + const article = new Article(); + + checkModelName(Article)('Article'); + + context('properties', () => { + [ + 'id', + 'userId', + 'slug', + 'title', + 'description', + 'body', + 'tagList', + 'readTime', + 'status', + 'createdAt', + 'updatedAt' + ].forEach(checkPropertyExists(article)); + }); + + context('check associations', () => { + const User = 'user'; + before(() => { + Article.associate({ User }); + }); + + it('defined a belongsTo association with User', () => { + expect(Article.belongsTo).to.have.been.calledWith(User); + }); + }); +}); diff --git a/src/tests/models/chatGroupMemberModel.test.js b/src/tests/models/chatGroupMemberModel.test.js new file mode 100644 index 00000000..e2b20e15 --- /dev/null +++ b/src/tests/models/chatGroupMemberModel.test.js @@ -0,0 +1,44 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import chai from 'chai'; +import sinonChai from 'sinon-chai'; + +import { + sequelize, dataTypes, checkModelName, checkPropertyExists +} from 'sequelize-test-helpers'; + +import ChatGroupMemberMemberModel from '../../models/chatgroupmember'; + +const { expect } = chai; +chai.use(sinonChai); + +// test chatgroupmember model +describe('src/models/ChatGroupMember', () => { + const ChatGroupMember = ChatGroupMemberMemberModel(sequelize, dataTypes); + const chatgroupmember = new ChatGroupMember(); + + checkModelName(ChatGroupMember)('ChatGroupMember'); + + context('properties', () => { + ['chatGroupId', 'userId', 'createdAt', 'updatedAt'].forEach( + checkPropertyExists(chatgroupmember) + ); + }); + + context('check associations', () => { + const User = 'user'; + const ChatGroup = 'chatgroup'; + + before(() => { + ChatGroupMember.associate({ User }); + ChatGroupMember.associate({ ChatGroup }); + }); + + it('defined a belongsTo association with User', () => { + expect(ChatGroupMember.belongsTo).to.have.been.calledWith(User); + }); + + it('defined a belongsTo association with User', () => { + expect(ChatGroupMember.belongsTo).to.have.been.calledWith(ChatGroup); + }); + }); +}); diff --git a/src/tests/models/chatGroupModel.test.js b/src/tests/models/chatGroupModel.test.js new file mode 100644 index 00000000..f9443dd4 --- /dev/null +++ b/src/tests/models/chatGroupModel.test.js @@ -0,0 +1,35 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import chai from 'chai'; +import sinonChai from 'sinon-chai'; + +import { + sequelize, dataTypes, checkModelName, checkPropertyExists +} from 'sequelize-test-helpers'; + +import ChatGroupModel from '../../models/chatgroup'; + +const { expect } = chai; +chai.use(sinonChai); + +// test chatgroup model +describe('src/models/ChatGroup', () => { + const ChatGroup = ChatGroupModel(sequelize, dataTypes); + const chatgroup = new ChatGroup(); + + checkModelName(ChatGroup)('ChatGroup'); + + context('properties', () => { + ['id', 'name', 'createdAt', 'updatedAt'].forEach(checkPropertyExists(chatgroup)); + }); + + context('check associations', () => { + const Chat = 'chat'; + before(() => { + ChatGroup.associate({ Chat }); + }); + + it('defined a hasMany association with Chat', () => { + expect(ChatGroup.hasMany).to.have.been.calledWith(Chat); + }); + }); +}); diff --git a/src/tests/models/chatModel.test.js b/src/tests/models/chatModel.test.js new file mode 100644 index 00000000..85765a61 --- /dev/null +++ b/src/tests/models/chatModel.test.js @@ -0,0 +1,43 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import chai from 'chai'; +import sinonChai from 'sinon-chai'; + +import { + sequelize, dataTypes, checkModelName, checkPropertyExists +} from 'sequelize-test-helpers'; + +import ChatModel from '../../models/chat'; + +const { expect } = chai; +chai.use(sinonChai); + +// test chat model +describe('src/models/Chat', () => { + const Chat = ChatModel(sequelize, dataTypes); + const chat = new Chat(); + + checkModelName(Chat)('Chat'); + + context('properties', () => { + ['id', 'userId', 'message', 'chatGroupId', 'createdAt', 'updatedAt'].forEach( + checkPropertyExists(chat) + ); + }); + + context('check associations', () => { + const User = 'user'; + const ChatGroup = 'chatgroup'; + before(() => { + Chat.associate({ User }); + Chat.associate({ ChatGroup }); + }); + + it('defined a belongsTo association with User', () => { + expect(Chat.belongsTo).to.have.been.calledWith(User); + }); + + it('defined a belongsTo association with ChatGroup', () => { + expect(Chat.belongsTo).to.have.been.calledWith(ChatGroup); + }); + }); +}); diff --git a/src/tests/models/commentModel.test.js b/src/tests/models/commentModel.test.js new file mode 100644 index 00000000..110475e0 --- /dev/null +++ b/src/tests/models/commentModel.test.js @@ -0,0 +1,37 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import chai from 'chai'; +import sinonChai from 'sinon-chai'; + +import { + sequelize, dataTypes, checkModelName, checkPropertyExists +} from 'sequelize-test-helpers'; + +import CommentModel from '../../models/comment'; + +const { expect } = chai; +chai.use(sinonChai); + +// test comment model +describe('src/models/Comment', () => { + const Comment = CommentModel(sequelize, dataTypes); + const comment = new Comment(); + + checkModelName(Comment)('Comment'); + + context('properties', () => { + ['id', 'articleSlug', 'userId', 'body', 'createdAt', 'updatedAt'].forEach( + checkPropertyExists(comment) + ); + }); + + context('check associations', () => { + const User = 'user'; + before(() => { + Comment.associate({ User }); + }); + + it('defined a belongsTo association with User', () => { + expect(Comment.belongsTo).to.have.been.calledWith(User); + }); + }); +}); diff --git a/src/tests/models/favoriteArticleModel.test.js b/src/tests/models/favoriteArticleModel.test.js new file mode 100644 index 00000000..ffa4c554 --- /dev/null +++ b/src/tests/models/favoriteArticleModel.test.js @@ -0,0 +1,37 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import chai from 'chai'; +import sinonChai from 'sinon-chai'; + +import { + sequelize, dataTypes, checkModelName, checkPropertyExists +} from 'sequelize-test-helpers'; + +import FavoriteArticleModel from '../../models/favoritearticle'; + +const { expect } = chai; +chai.use(sinonChai); + +// test favoritearticle model +describe('src/models/FavoriteArticle', () => { + const FavoriteArticle = FavoriteArticleModel(sequelize, dataTypes); + const favoritearticle = new FavoriteArticle(); + + checkModelName(FavoriteArticle)('FavoriteArticle'); + + context('properties', () => { + ['userId', 'articleSlug', 'createdAt', 'updatedAt'].forEach( + checkPropertyExists(favoritearticle) + ); + }); + + context('check associations', () => { + const User = 'user'; + before(() => { + FavoriteArticle.associate({ User }); + }); + + it('defined a belongsTo association with User', () => { + expect(FavoriteArticle.belongsTo).to.have.been.calledWith(User); + }); + }); +}); diff --git a/src/tests/models/index.js b/src/tests/models/index.js new file mode 100644 index 00000000..cbcaaa6f --- /dev/null +++ b/src/tests/models/index.js @@ -0,0 +1,11 @@ +import './userModel.test'; +import './articleModel.test'; +import './commentModel.test'; +import './tokenModel.test'; +import './articleBookmarkModel.test'; +import './chatModel.test'; +import './chatGroupModel.test'; +import './chatGroupMemberModel.test'; +import './favoriteArticleModel.test'; +import './notificationModel.test'; +import './notificationconfigModel.test'; diff --git a/src/tests/models/notificationModel.test.js b/src/tests/models/notificationModel.test.js new file mode 100644 index 00000000..d2738883 --- /dev/null +++ b/src/tests/models/notificationModel.test.js @@ -0,0 +1,37 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import chai from 'chai'; +import sinonChai from 'sinon-chai'; + +import { + sequelize, dataTypes, checkModelName, checkPropertyExists +} from 'sequelize-test-helpers'; + +import NotificationModel from '../../models/notification'; + +const { expect } = chai; +chai.use(sinonChai); + +// test notification model +describe('src/models/Notification', () => { + const Notification = NotificationModel(sequelize, dataTypes); + const notification = new Notification(); + + checkModelName(Notification)('Notification'); + + context('properties', () => { + ['userId', 'message', 'status', 'createdAt', 'updatedAt'].forEach( + checkPropertyExists(notification) + ); + }); + + context('check associations', () => { + const User = 'user'; + before(() => { + Notification.associate({ User }); + }); + + it('defined a belongsTo association with User', () => { + expect(Notification.belongsTo).to.have.been.calledWith(User); + }); + }); +}); diff --git a/src/tests/models/notificationconfigModel.test.js b/src/tests/models/notificationconfigModel.test.js new file mode 100644 index 00000000..d8fc8b9a --- /dev/null +++ b/src/tests/models/notificationconfigModel.test.js @@ -0,0 +1,35 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import chai from 'chai'; +import sinonChai from 'sinon-chai'; + +import { + sequelize, dataTypes, checkModelName, checkPropertyExists +} from 'sequelize-test-helpers'; + +import NotificationConfigConfigModel from '../../models/notificationconfig'; + +const { expect } = chai; +chai.use(sinonChai); + +// test notificationconfig model +describe('src/models/NotificationConfig', () => { + const NotificationConfig = NotificationConfigConfigModel(sequelize, dataTypes); + const notificationconfig = new NotificationConfig(); + + checkModelName(NotificationConfig)('NotificationConfig'); + + context('properties', () => { + ['userId', 'config', 'createdAt', 'updatedAt'].forEach(checkPropertyExists(notificationconfig)); + }); + + context('check associations', () => { + const User = 'user'; + before(() => { + NotificationConfig.associate({ User }); + }); + + it('defined a belongsTo association with User', () => { + expect(NotificationConfig.belongsTo).to.have.been.calledWith(User); + }); + }); +}); diff --git a/src/tests/models/tokenModel.test.js b/src/tests/models/tokenModel.test.js new file mode 100644 index 00000000..168658bb --- /dev/null +++ b/src/tests/models/tokenModel.test.js @@ -0,0 +1,35 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import chai from 'chai'; +import sinonChai from 'sinon-chai'; + +import { + sequelize, dataTypes, checkModelName, checkPropertyExists +} from 'sequelize-test-helpers'; + +import TokenModel from '../../models/token'; + +const { expect } = chai; +chai.use(sinonChai); + +// test token model +describe('src/models/Token', () => { + const Token = TokenModel(sequelize, dataTypes); + const token = new Token(); + + checkModelName(Token)('Token'); + + context('properties', () => { + ['id', 'userId', 'token', 'createdAt', 'updatedAt'].forEach(checkPropertyExists(token)); + }); + + context('check associations', () => { + const User = 'user'; + before(() => { + Token.associate({ User }); + }); + + it('defined a belongsTo association with User', () => { + expect(Token.belongsTo).to.have.been.calledWith(User); + }); + }); +}); diff --git a/src/tests/models/userModel.test.js b/src/tests/models/userModel.test.js new file mode 100644 index 00000000..5af1721b --- /dev/null +++ b/src/tests/models/userModel.test.js @@ -0,0 +1,53 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import chai from 'chai'; +import sinonChai from 'sinon-chai'; + +import { + sequelize, dataTypes, checkModelName, checkPropertyExists +} from 'sequelize-test-helpers'; + +import UserModel from '../../models/user'; + +const { expect } = chai; +chai.use(sinonChai); + +// test user model +describe('src/models/User', () => { + const User = UserModel(sequelize, dataTypes); + const user = new User(); + + checkModelName(User)('User'); + + context('properties', () => { + [ + 'id', + 'firstName', + 'lastName', + 'username', + 'email', + 'password', + 'bio', + 'image', + 'role', + 'permissions', + 'accountProvider', + 'accountProviderUserId', + 'createdAt', + 'updatedAt' + ].forEach(checkPropertyExists(user)); + }); + + context('check associations', () => { + const Article = 'article'; + const Comment = 'comment'; + before(() => { + User.associate({ Article }); + User.associate({ Comment }); + }); + + it('defined a hasMany association with User', () => { + expect(User.hasMany).to.have.been.calledWith(Article); + expect(User.hasMany).to.have.been.calledWith(Comment); + }); + }); +}); diff --git a/src/tests/queries/articleBookmarks/bookmarkArticle.test.js b/src/tests/queries/articleBookmarks/bookmarkArticle.test.js new file mode 100644 index 00000000..653e5ce2 --- /dev/null +++ b/src/tests/queries/articleBookmarks/bookmarkArticle.test.js @@ -0,0 +1,54 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import chai from 'chai'; +import db from '../../../models'; +import { Article } from '../../../queries'; +import * as Factory from '../../../helpers/factory'; + +const { expect } = chai; + +let createdUser = ''; +let createdArticle = ''; + +const user = Factory.user.build(); +const article = Factory.article.build(); + +delete user.id; +delete article.id; + +describe('Bookmark article query', () => { + before(async () => { + try { + await db.User.destroy({ + truncate: true, + cascade: true, + logging: false + }); + createdUser = (await db.User.create(user, { logging: false })).dataValues; + article.userId = createdUser.id; + createdArticle = (await db.Article.create(article, { logging: false })).dataValues; + } catch (error) { + throw error; + } + }); + + it('should bookmark an article', async () => { + const bookmarkedArticle = await Article.bookmark.add(createdUser.id, createdArticle.slug); + expect(Object.keys(bookmarkedArticle).length).to.be.above(0); + expect(bookmarkedArticle).to.not.include.keys('errors'); + }); + + it('should not bookmark an article if the user has already bookmarked it', async () => { + const bookmarkedArticle = await Article.bookmark.add(createdUser.id, createdArticle.slug); + expect(bookmarkedArticle).to.include.keys('errors'); + }); + + it('should not bookmark an article if the article slug is not provided', async () => { + const bookmarkedArticle = await Article.bookmark.add(createdUser.id); + expect(bookmarkedArticle).to.include.keys('errors'); + }); + + it('should not bookmark an article if no parameter is provided', async () => { + const bookmarkedArticle = await Article.bookmark.add(); + expect(bookmarkedArticle).to.include.keys('errors'); + }); +}); diff --git a/src/tests/queries/articleBookmarks/getAllBookmarkedArticles.test.js b/src/tests/queries/articleBookmarks/getAllBookmarkedArticles.test.js new file mode 100644 index 00000000..8e82d8c5 --- /dev/null +++ b/src/tests/queries/articleBookmarks/getAllBookmarkedArticles.test.js @@ -0,0 +1,61 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import chai from 'chai'; +import db from '../../../models'; +import { Article } from '../../../queries'; +import * as Factory from '../../../helpers/factory'; + +const { expect } = chai; + +let createdUser = ''; +let createdArticle = ''; +let bookmarkedArticles = ''; + +const user = Factory.user.build(); +const article = Factory.article.build(); + +delete user.id; +delete article.id; + +describe('Get Bookmarks article query', () => { + before(async () => { + try { + await db.User.destroy({ + truncate: true, + cascade: true, + logging: false + }); + createdUser = (await db.User.create(user, { logging: false })).dataValues; + article.userId = createdUser.id; + createdArticle = (await db.Article.create(article, { logging: false })).dataValues; + await db.ArticleBookmark.create( + { userId: createdUser.id, articleSlug: createdArticle.slug }, + { logging: false } + ); + } catch (error) { + throw error; + } + }); + + it('should get all bookmarks', async () => { + bookmarkedArticles = await Article.bookmark.getAll(createdUser.id); + expect(bookmarkedArticles.length).to.be.above(0); + expect(bookmarkedArticles).to.not.include.keys('errors'); + }); + + it('should not get all bookmarks if the user ID is not found', async () => { + bookmarkedArticles = await Article.bookmark.getAll(0); + expect(bookmarkedArticles.length).be.equal(0); + expect(bookmarkedArticles).to.not.include.keys('errors'); + }); + + it('should not get all bookmarks if no provided parameter', async () => { + bookmarkedArticles = await Article.bookmark.getAll(); + expect(bookmarkedArticles.length).be.equal(0); + expect(bookmarkedArticles).to.not.include.keys('errors'); + }); + + it('should not throw an error if the user ID is not valid', async () => { + bookmarkedArticles = await Article.bookmark.getAll(['~~~']); + expect(bookmarkedArticles).to.include.keys('errors'); + }); +}); diff --git a/src/tests/queries/articleBookmarks/index.js b/src/tests/queries/articleBookmarks/index.js new file mode 100644 index 00000000..b243664c --- /dev/null +++ b/src/tests/queries/articleBookmarks/index.js @@ -0,0 +1,3 @@ +import './bookmarkArticle.test'; +import './getAllBookmarkedArticles.test'; +import './removeBookmarkedArticle.test'; diff --git a/src/tests/queries/articleBookmarks/removeBookmarkedArticle.test.js b/src/tests/queries/articleBookmarks/removeBookmarkedArticle.test.js new file mode 100644 index 00000000..f0caed08 --- /dev/null +++ b/src/tests/queries/articleBookmarks/removeBookmarkedArticle.test.js @@ -0,0 +1,60 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import chai from 'chai'; +import db from '../../../models'; +import { Article } from '../../../queries'; +import * as Factory from '../../../helpers/factory'; + +const { expect } = chai; + +let createdUser = ''; +let createdArticle = ''; +let bookmarkedArticles = ''; + +const user = Factory.user.build(); +const article = Factory.article.build(); + +delete user.id; +delete article.id; + +describe('Delete Bookmark article query', () => { + before(async () => { + try { + await db.User.destroy({ + truncate: true, + cascade: true, + logging: false + }); + createdUser = (await db.User.create(user, { logging: false })).dataValues; + article.userId = createdUser.id; + createdArticle = (await db.Article.create(article, { logging: false })).dataValues; + await db.ArticleBookmark.create( + { userId: createdUser.id, articleSlug: createdArticle.slug }, + { logging: false } + ); + } catch (error) { + throw error; + } + }); + + it('should remove a bookmark', async () => { + bookmarkedArticles = await Article.bookmark.remove(createdUser.id, createdArticle.slug); + expect(bookmarkedArticles).to.be.above(0); + expect(bookmarkedArticles).to.not.include.keys('errors'); + }); + + it('should not remove a bookmark if the article slug is not found', async () => { + bookmarkedArticles = await Article.bookmark.remove(createdUser.id, 'invalid-slug'); + expect(bookmarkedArticles).be.equal(0); + expect(bookmarkedArticles).to.not.include.keys('errors'); + }); + + it('should not remove a bookmark if no provided parameter', async () => { + bookmarkedArticles = await Article.bookmark.remove(); + expect(bookmarkedArticles).be.equal(null); + }); + + it('should throw an error if the parameters are not valid', async () => { + bookmarkedArticles = await Article.bookmark.remove({}, {}); + expect(bookmarkedArticles).to.include.keys('errors'); + }); +}); diff --git a/src/tests/queries/articles/create.test.js b/src/tests/queries/articles/create.test.js new file mode 100644 index 00000000..c7e0ddb7 --- /dev/null +++ b/src/tests/queries/articles/create.test.js @@ -0,0 +1,36 @@ +import chai from 'chai'; +import chaiHttp from 'chai-http'; +import { Article, User } from '../../../queries'; +import * as Factory from '../../../helpers/factory'; +import * as helpers from '../../../helpers'; + +const { expect } = chai; + +const article = Factory.article.build(); +const user = Factory.user.build(); + +chai.use(chaiHttp); +describe('Query to create article', () => { + it('should create aricle', async () => { + const newUser = await User.create(user); + article.userId = newUser.id; + article.status = 'published'; + article.slug = helpers.generator.slug(article.title); + const response = await Article.create(article); + expect(response).to.include.keys('dataValues'); + expect(response.dataValues).to.include.keys('id'); + expect(response.dataValues).to.include.keys('title'); + expect(response.dataValues).to.include.keys('body'); + expect(response.dataValues).to.include.keys('description'); + expect(response.dataValues).to.include.keys('status'); + expect(response.dataValues).to.include.keys('readTime'); + expect(response.dataValues).to.include.keys('coverUrl'); + expect(response.dataValues).to.include.keys('tagList'); + expect(response.dataValues).to.include.keys('slug'); + expect(response.dataValues).to.include.keys('tagList'); + expect(response.dataValues).to.include.keys('updatedAt'); + expect(response.dataValues).to.include.keys('createdAt'); + expect(response.dataValues).to.include.keys('favorited'); + expect(response.dataValues).to.include.keys('favoritesCount'); + }); +}); diff --git a/src/tests/queries/articles/get.test.js b/src/tests/queries/articles/get.test.js new file mode 100644 index 00000000..889579c3 --- /dev/null +++ b/src/tests/queries/articles/get.test.js @@ -0,0 +1,38 @@ +import chai from 'chai'; +import chaiHttp from 'chai-http'; +import { Article } from '../../../queries'; +import * as Factory from '../../../helpers/factory'; +import db from '../../../models'; + +const { expect } = chai; + +const article = Factory.article.build(); +delete article.id; +chai.use(chaiHttp); +describe('Query to get article', () => { + it('should get one article', async () => { + const findArticle = await db.Article.findAll({ limit: 1, logging: false }); + const { slug } = findArticle[0].dataValues; + const response = await Article.get({ slug }); + expect(Object.keys(response).length).to.be.above(0); + expect(response).to.include.keys('dataValues'); + expect(response.dataValues).to.include.keys('id'); + expect(response.dataValues).to.include.keys('title'); + expect(response.dataValues).to.include.keys('body'); + expect(response.dataValues).to.include.keys('description'); + expect(response.dataValues).to.include.keys('status'); + expect(response.dataValues).to.include.keys('readTime'); + expect(response.dataValues).to.include.keys('coverUrl'); + expect(response.dataValues).to.include.keys('tagList'); + expect(response.dataValues).to.include.keys('slug'); + expect(response.dataValues).to.include.keys('tagList'); + expect(response.dataValues).to.include.keys('updatedAt'); + expect(response.dataValues).to.include.keys('createdAt'); + expect(response.dataValues).to.include.keys('favorited'); + expect(response.dataValues).to.include.keys('favoritesCount'); + }); + it('should get article', async () => { + const response = await Article.get(); + expect(Object.keys(response).length).to.be.above(0); + }); +}); diff --git a/src/tests/queries/articles/getAll.test.js b/src/tests/queries/articles/getAll.test.js new file mode 100644 index 00000000..422e5857 --- /dev/null +++ b/src/tests/queries/articles/getAll.test.js @@ -0,0 +1,23 @@ +import chai from 'chai'; +import chaiHttp from 'chai-http'; +import { getAll } from '../../../queries/articles'; +import * as Factory from '../../../helpers/factory'; + +const { expect } = chai; + +const article = Factory.article.build(); +delete article.id; +chai.use(chaiHttp); +describe('Query to get article', () => { + it('should get all articles', async () => { + const LIMIT = 2; + const OFFSET = 0; + const data = { + keyword: undefined, + author: undefined, + tag: undefined + }; + const newArticle = await getAll(LIMIT, OFFSET, data); + expect(Object.keys(newArticle).length).to.be.above(0); + }); +}); diff --git a/src/tests/queries/articles/index.js b/src/tests/queries/articles/index.js new file mode 100644 index 00000000..11f2ba89 --- /dev/null +++ b/src/tests/queries/articles/index.js @@ -0,0 +1,6 @@ +import './create.test'; +import './get.test'; +import './getAll.test'; +import './update.test'; +import './pagination.test'; +import './searchFilters.test'; diff --git a/src/tests/queries/articles/pagination.test.js b/src/tests/queries/articles/pagination.test.js new file mode 100644 index 00000000..b264bbac --- /dev/null +++ b/src/tests/queries/articles/pagination.test.js @@ -0,0 +1,15 @@ +import chai from 'chai'; +import chaiHttp from 'chai-http'; +import { getAll } from '../../../queries/articles'; + +const { expect } = chai; + +chai.use(chaiHttp); +describe('Pagination query', async () => { + it('should get article by pages', async () => { + const LIMIT = 1; + const OFFSET = 0; + const newArticle = getAll(LIMIT, OFFSET); // getAll() parameters: limit and offset + expect(Object.keys(newArticle).length).to.be.equal(0); + }); +}); diff --git a/src/tests/queries/articles/searchFilters.test.js b/src/tests/queries/articles/searchFilters.test.js new file mode 100644 index 00000000..7c012daf --- /dev/null +++ b/src/tests/queries/articles/searchFilters.test.js @@ -0,0 +1,31 @@ +import chai from 'chai'; +import chaiHttp from 'chai-http'; +import * as Factory from '../../../helpers/factory'; +import { Article } from '../../../queries'; +import db from '../../../models'; +import { filterQueryBuilder } from '../../../helpers/searchArticleFilters'; + +const { expect } = chai; + +const article = Factory.article.build(); +const user = Factory.user.build(); +delete user.id; +delete article.id; +delete article.slug; +delete article.tagList; +chai.use(chaiHttp); +describe('Search query builder', () => { + it('should test query builder', () => { + const query = filterQueryBuilder(); + expect(Object.keys(query).length).to.be.above(0); + }); + it('should test query builder with params', () => { + const data = { + author: 'John', + tag: 'kigali', + keyword: 'es6' + }; + const query = filterQueryBuilder(data); + expect(Object.keys(query).length).to.be.above(0); + }); +}); diff --git a/src/tests/queries/articles/update.test.js b/src/tests/queries/articles/update.test.js new file mode 100644 index 00000000..437e2c11 --- /dev/null +++ b/src/tests/queries/articles/update.test.js @@ -0,0 +1,49 @@ +import chai from 'chai'; +import chaiHttp from 'chai-http'; +import { Article } from '../../../queries'; +import db from '../../../models'; +import * as Factory from '../../../helpers/factory'; + +chai.use(chaiHttp); + +let createdUser = ''; +let createdArticle = ''; + +const user = Factory.user.build(); +const article = Factory.article.build(); + +delete user.id; +delete article.id; +// This is to test the query that is used to update data of a given article +describe('Query to edit an article', () => { + before(async () => { + try { + await db.User.destroy({ + truncate: true, + cascade: true, + logging: false + }); + createdUser = (await db.User.create(user, { logging: false })).dataValues; + article.userId = createdUser.id; + createdArticle = (await db.Article.create(article, { logging: false })).dataValues; + } catch (error) { + throw error; + } + }); + it('should edit an article', async () => { + const response = await Article.update( + { + title: 'aaaaavfff yyyy' + }, + createdArticle.slug + ); + response.should.be.an('object'); + Object.keys(response).length.should.above(0); + }); + it('should fail to edit article', async () => { + // find article to update + const response = await Article.update(); + response.should.be.an('object'); + Object.keys(response).length.should.equal(0); + }); +}); diff --git a/src/tests/queries/chats/getAllChats.test.js b/src/tests/queries/chats/getAllChats.test.js new file mode 100644 index 00000000..739dd6e0 --- /dev/null +++ b/src/tests/queries/chats/getAllChats.test.js @@ -0,0 +1,47 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import chai from 'chai'; +import db from '../../../models'; +import { Chat } from '../../../queries'; +import * as Factory from '../../../helpers/factory'; + +const { expect } = chai; + +let createdUser = ''; +let createdChat = ''; +const user = Factory.user.build(); +const message = 'Hello'; + +delete user.id; + +describe('Get chats query', () => { + before(async () => { + try { + await db.User.destroy({ + truncate: true, + cascade: true, + logging: false + }); + createdUser = (await db.User.create(user, { logging: false })).dataValues; + createdChat = (await db.Chat.create({ userId: createdUser.id, message }, { logging: false })) + .dataValues; + } catch (error) { + throw error; + } + }); + + it('should get chats', async () => { + const chats = await Chat.getAll(); + expect(chats.length).to.be.above(0); + expect(chats).to.not.include.keys('errors'); + }); + + it('should not get chats if there are no more chats', async () => { + const chats = await Chat.getAll(100); + expect(chats.length).to.be.equal(0); + }); + + it('should not get chats if the parameters passed are not numbers', async () => { + const chats = await Chat.getAll({}, {}); + expect(chats).to.include.keys('errors'); + }); +}); diff --git a/src/tests/queries/chats/index.js b/src/tests/queries/chats/index.js new file mode 100644 index 00000000..e8f267f8 --- /dev/null +++ b/src/tests/queries/chats/index.js @@ -0,0 +1,3 @@ +import './saveChat.test'; +import './getAllChats.test'; +import './removeChat.test'; diff --git a/src/tests/queries/chats/removeChat.test.js b/src/tests/queries/chats/removeChat.test.js new file mode 100644 index 00000000..be40c94a --- /dev/null +++ b/src/tests/queries/chats/removeChat.test.js @@ -0,0 +1,70 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import chai from 'chai'; +import db from '../../../models'; +import { Chat } from '../../../queries'; +import * as Factory from '../../../helpers/factory'; + +const { expect } = chai; + +let createdUser = ''; +let createdChatOne = ''; +let createdChatTwo = ''; +const user = Factory.user.build(); +const message = 'Hello'; + +delete user.id; + +describe('Remove chat query', () => { + before(async () => { + try { + await db.User.destroy({ + truncate: true, + cascade: true, + logging: false + }); + createdUser = (await db.User.create(user, { logging: false })).dataValues; + createdChatOne = (await db.Chat.create( + { userId: createdUser.id, message }, + { logging: false } + )).dataValues; + createdChatTwo = (await db.Chat.create( + { userId: createdUser.id, message }, + { logging: false } + )).dataValues; + } catch (error) { + throw error; + } + }); + + it('should remove a chat', async () => { + const removedChat = await Chat.remove(createdChatOne.id); + expect(removedChat).to.be.above(0); + expect(removedChat).to.not.include.keys('errors'); + }); + + it('should not remove a chat if the chat ID is not found', async () => { + const removedChat = await Chat.remove(0); + expect(removedChat).to.be.equal(0); + }); + + it('should not remove a chat if the user ID is not found', async () => { + const removedChat = await Chat.remove(createdChatTwo.id, createdUser.id * 2); + expect(removedChat).to.be.equal(0); + }); + + it('should remove a chat', async () => { + const removedChat = await Chat.remove(createdChatTwo.id, createdUser.id); + expect(removedChat).to.be.above(0); + expect(removedChat).to.not.include.keys('errors'); + }); + + it('should not remove a chat if the parameter passed is invalid', async () => { + const removedChat = await Chat.remove({}); + expect(removedChat).to.include.keys('errors'); + }); + + it('should not remove a chat if there is no passed parameter', async () => { + const removedChat = await Chat.remove(); + expect(removedChat).to.be.equal(0); + }); +}); diff --git a/src/tests/queries/chats/saveChat.test.js b/src/tests/queries/chats/saveChat.test.js new file mode 100644 index 00000000..0b34ded2 --- /dev/null +++ b/src/tests/queries/chats/saveChat.test.js @@ -0,0 +1,54 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import chai from 'chai'; +import db from '../../../models'; +import { Chat } from '../../../queries'; +import * as Factory from '../../../helpers/factory'; + +const { expect } = chai; + +let createdUser = ''; +const user = Factory.user.build(); +const message = 'Hello'; + +delete user.id; + +describe('Save chat query', () => { + before(async () => { + try { + await db.User.destroy({ + truncate: true, + cascade: true, + logging: false + }); + createdUser = (await db.User.create(user, { logging: false })).dataValues; + } catch (error) { + throw error; + } + }); + + it('should save a chat', async () => { + const savedChat = await Chat.save(createdUser.id, message); + expect(Object.keys(savedChat).length).to.be.above(0); + expect(savedChat).to.not.include.keys('errors'); + }); + + it('should not save a chat if the user does not exist', async () => { + const savedChat = await Chat.save(0, message); + expect(savedChat).to.include.keys('errors'); + }); + + it('should not save a chat if the message is empty', async () => { + const savedChat = await Chat.save(createdUser.id, null); + expect(savedChat).to.include.keys('errors'); + }); + + it('should not save a chat if the parameter passed is invalid', async () => { + const savedChat = await Chat.save('~~~'); + expect(savedChat).to.include.keys('errors'); + }); + + it('should not save a chat if there is no passed parameter', async () => { + const savedChat = await Chat.save(); + expect(savedChat).to.include.keys('errors'); + }); +}); diff --git a/src/tests/queries/favoriteArticles/favoriteArticle.test.js b/src/tests/queries/favoriteArticles/favoriteArticle.test.js new file mode 100644 index 00000000..cd70c8fa --- /dev/null +++ b/src/tests/queries/favoriteArticles/favoriteArticle.test.js @@ -0,0 +1,54 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import chai from 'chai'; +import db from '../../../models'; +import { Article } from '../../../queries'; +import * as Factory from '../../../helpers/factory'; + +const { expect } = chai; + +let createdUser = ''; +let createdArticle = ''; + +const user = Factory.user.build(); +const article = Factory.article.build(); + +delete user.id; +delete article.id; + +describe('Favorite article query', () => { + before(async () => { + try { + await db.User.destroy({ + truncate: true, + cascade: true, + logging: false + }); + createdUser = (await db.User.create(user, { logging: false })).dataValues; + article.userId = createdUser.id; + createdArticle = (await db.Article.create(article, { logging: false })).dataValues; + } catch (error) { + throw error; + } + }); + + it('should favorite an article', async () => { + const favoritedArticle = await Article.favorite.add(createdUser.id, createdArticle.slug, 1); + expect(Object.keys(favoritedArticle).length).to.be.above(0); + expect(favoritedArticle).to.not.include.keys('errors'); + }); + + it('should not favorite an article if the user has already favorited it', async () => { + const favoritedArticle = await Article.favorite.add(createdUser.id, createdArticle.slug, 1); + expect(favoritedArticle).to.include.keys('errors'); + }); + + it('should not favorite an article if the article slug is not provided', async () => { + const favoritedArticle = await Article.favorite.add(createdUser.id); + expect(favoritedArticle).to.include.keys('errors'); + }); + + it('should not favorite an article if no parameter is provided', async () => { + const favoritedArticle = await Article.favorite.add(); + expect(favoritedArticle).to.include.keys('errors'); + }); +}); diff --git a/src/tests/queries/favoriteArticles/getAllFavoritedArticles.test.js b/src/tests/queries/favoriteArticles/getAllFavoritedArticles.test.js new file mode 100644 index 00000000..041710a3 --- /dev/null +++ b/src/tests/queries/favoriteArticles/getAllFavoritedArticles.test.js @@ -0,0 +1,61 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import chai from 'chai'; +import db from '../../../models'; +import { Article } from '../../../queries'; +import * as Factory from '../../../helpers/factory'; + +const { expect } = chai; + +let createdUser = ''; +let createdArticle = ''; +let favoritedArticles = ''; + +const user = Factory.user.build(); +const article = Factory.article.build(); + +delete user.id; +delete article.id; + +describe('Get favorited articles query', () => { + before(async () => { + try { + await db.User.destroy({ + truncate: true, + cascade: true, + logging: false + }); + createdUser = (await db.User.create(user, { logging: false })).dataValues; + article.userId = createdUser.id; + createdArticle = (await db.Article.create(article, { logging: false })).dataValues; + await db.FavoriteArticle.create( + { userId: createdUser.id, articleSlug: createdArticle.slug }, + { logging: false } + ); + } catch (error) { + throw error; + } + }); + + it('should get all favorited articles', async () => { + favoritedArticles = await Article.favorite.getAll(createdUser.id); + expect(favoritedArticles.length).to.be.above(0); + expect(favoritedArticles).to.not.include.keys('errors'); + }); + + it('should not get all favorited articles if the user ID is not found', async () => { + favoritedArticles = await Article.favorite.getAll(0); + expect(favoritedArticles.length).be.equal(0); + expect(favoritedArticles).to.not.include.keys('errors'); + }); + + it('should not get all favorited articles if no provided parameter', async () => { + favoritedArticles = await Article.favorite.getAll(); + expect(favoritedArticles.length).be.equal(0); + expect(favoritedArticles).to.not.include.keys('errors'); + }); + + it('should not throw an error if the user ID is not valid', async () => { + favoritedArticles = await Article.favorite.getAll(['~~~']); + expect(favoritedArticles).to.include.keys('errors'); + }); +}); diff --git a/src/tests/queries/favoriteArticles/index.js b/src/tests/queries/favoriteArticles/index.js new file mode 100644 index 00000000..43235fcf --- /dev/null +++ b/src/tests/queries/favoriteArticles/index.js @@ -0,0 +1,3 @@ +import './favoriteArticle.test'; +import './getAllFavoritedArticles.test'; +import './removeFavoritedArticle.test'; diff --git a/src/tests/queries/favoriteArticles/removeFavoritedArticle.test.js b/src/tests/queries/favoriteArticles/removeFavoritedArticle.test.js new file mode 100644 index 00000000..79ad0f87 --- /dev/null +++ b/src/tests/queries/favoriteArticles/removeFavoritedArticle.test.js @@ -0,0 +1,60 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import chai from 'chai'; +import db from '../../../models'; +import { Article } from '../../../queries'; +import * as Factory from '../../../helpers/factory'; + +const { expect } = chai; + +let createdUser = ''; +let createdArticle = ''; +let favoritedArticle = ''; + +const user = Factory.user.build(); +const article = Factory.article.build(); + +delete user.id; +delete article.id; + +describe('Delete favorited article query', () => { + before(async () => { + try { + await db.User.destroy({ + truncate: true, + cascade: true, + logging: false + }); + createdUser = (await db.User.create(user, { logging: false })).dataValues; + article.userId = createdUser.id; + createdArticle = (await db.Article.create(article, { logging: false })).dataValues; + await db.FavoriteArticle.create( + { userId: createdUser.id, articleSlug: createdArticle.slug }, + { logging: false } + ); + } catch (error) { + throw error; + } + }); + + it('should remove a favorited article', async () => { + favoritedArticle = await Article.favorite.remove(createdUser.id, createdArticle.slug, 1); + expect(favoritedArticle).to.be.above(0); + expect(favoritedArticle).to.not.include.keys('errors'); + }); + + it('should not remove a favorited article if the article slug is not found', async () => { + favoritedArticle = await Article.favorite.remove(createdUser.id, 'invalid-slug', 1); + expect(favoritedArticle).be.equal(0); + expect(favoritedArticle).to.not.include.keys('errors'); + }); + + it('should not remove a favorited article if no provided parameter', async () => { + favoritedArticle = await Article.favorite.remove(); + expect(favoritedArticle).to.include.keys('errors'); + }); + + it('should throw an error if the parameters are not valid', async () => { + favoritedArticle = await Article.favorite.remove({}, {}, 1); + expect(favoritedArticle).to.include.keys('errors'); + }); +}); diff --git a/src/tests/queries/gallery/get.test.js b/src/tests/queries/gallery/get.test.js new file mode 100644 index 00000000..d7f69752 --- /dev/null +++ b/src/tests/queries/gallery/get.test.js @@ -0,0 +1,37 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import chai from 'chai'; +import db from '../../../models'; +import { Gallery } from '../../../queries'; +import * as Factory from '../../../helpers/factory'; + +const { expect } = chai; + +let createdUser = ''; +let createdGallery = ''; +const user = Factory.user.build(); + +delete user.id; + +describe('Get gallery query', () => { + before(async () => { + try { + await db.User.destroy({ + truncate: true, + cascade: true, + logging: false + }); + createdUser = (await db.User.create(user, { logging: false })).dataValues; + createdGallery = (await db.Gallery.create( + { image: 'placeholder.png', userId: createdUser.id }, + { logging: false } + )).dataValues; + } catch (error) { + throw error; + } + }); + + it('should get chats', async () => { + const galleries = await Gallery.get(); + expect(galleries.length).to.be.above(0); + }); +}); diff --git a/src/tests/queries/gallery/index.js b/src/tests/queries/gallery/index.js new file mode 100644 index 00000000..21e0e590 --- /dev/null +++ b/src/tests/queries/gallery/index.js @@ -0,0 +1,4 @@ +import save from './save.test'; +import get from './get.test'; + +export { save, get }; diff --git a/src/tests/queries/gallery/save.test.js b/src/tests/queries/gallery/save.test.js new file mode 100644 index 00000000..6d641353 --- /dev/null +++ b/src/tests/queries/gallery/save.test.js @@ -0,0 +1,32 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import chai from 'chai'; +import db from '../../../models'; +import { Gallery } from '../../../queries'; +import * as Factory from '../../../helpers/factory'; + +const { expect } = chai; + +let createdUser = ''; +const user = Factory.user.build(); + +delete user.id; + +describe('Save gallery query', () => { + before(async () => { + try { + await db.User.destroy({ + truncate: true, + cascade: true, + logging: false + }); + createdUser = (await db.User.create(user, { logging: false })).dataValues; + } catch (error) { + throw error; + } + }); + + it('should save a gallery', async () => { + const savedGallery = await Gallery.save({ image: 'placeholder.png', userId: createdUser.id }); + expect(Object.keys(savedGallery).length).to.be.above(0); + }); +}); diff --git a/src/tests/queries/highlights/findOrCreateUser.test.js b/src/tests/queries/highlights/findOrCreateUser.test.js new file mode 100644 index 00000000..715f3ddc --- /dev/null +++ b/src/tests/queries/highlights/findOrCreateUser.test.js @@ -0,0 +1,59 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import chai from 'chai'; +import db from '../../../models'; +import { findOrCreate } from '../../../queries/highlights'; +import * as Factory from '../../../helpers/factory'; + +const { expect } = chai; + +const article = Factory.article.build(); +const user = Factory.user.build(); +delete article.id; +delete user.id; + +let createdArticle = ''; +let createdUser = ''; + +let highlight = ''; + +describe('Find or create highlight query', () => { + before(async () => { + try { + await db.User.destroy({ + truncate: true, + cascade: true, + logging: false + }); + createdUser = (await db.User.create(user, { logging: false })).get(); + article.userId = createdUser.id; + createdArticle = (await db.Article.create(article, { logging: false })).dataValues; + highlight = { + articleSlug: createdArticle.slug, + userId: createdUser.id, + highlightedText: createdArticle.body.substring(0, 2), + startIndex: 0, + stopIndex: 2, + comment: 'hey here' + }; + } catch (error) { + throw error; + } + }); + + it('should throw an error message', async () => { + const newOrExistingHighlight = await findOrCreate(); + expect(newOrExistingHighlight).to.include.keys('errors'); + }); + + it('should create a highlight account', async () => { + const newOrExistingHighlight = await findOrCreate(highlight); + expect(Object.keys(newOrExistingHighlight[0]).length).to.be.above(0); + expect(newOrExistingHighlight[1]).to.be.equal(true); + }); + + it('should not create a highlight account', async () => { + const newOrExistingHighlight = await findOrCreate(highlight); + expect(Object.keys(newOrExistingHighlight[0]).length).to.be.above(0); + expect(newOrExistingHighlight[1]).to.be.equal(false); + }); +}); diff --git a/src/tests/queries/highlights/index.js b/src/tests/queries/highlights/index.js new file mode 100644 index 00000000..259008d4 --- /dev/null +++ b/src/tests/queries/highlights/index.js @@ -0,0 +1 @@ +import './findOrCreateUser.test'; diff --git a/src/tests/queries/index.js b/src/tests/queries/index.js new file mode 100644 index 00000000..f7622438 --- /dev/null +++ b/src/tests/queries/index.js @@ -0,0 +1,13 @@ +import './users'; +import './articles'; +import './tags'; +import './tokens'; +import './articleBookmarks'; +import './chats'; +import './favoriteArticles'; +import './permissions/createPermission.test'; +import './highlights'; +import './notifications'; +import './notificationsConfig'; +import './gallery'; +import './ratings'; diff --git a/src/tests/queries/notifications/createNotification.test.js b/src/tests/queries/notifications/createNotification.test.js new file mode 100644 index 00000000..921bcaf5 --- /dev/null +++ b/src/tests/queries/notifications/createNotification.test.js @@ -0,0 +1,50 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import chai from 'chai'; +import db from '../../../models'; +import { Notification } from '../../../queries'; +import * as Factory from '../../../helpers/factory'; + +const { expect } = chai; + +let createdUser = ''; + +const user = Factory.user.build(); +const notification = Factory.notification.build(); + +delete user.id; + +describe('Create notification query', () => { + before(async () => { + try { + await db.User.destroy({ + truncate: true, + cascade: true, + logging: false + }); + createdUser = (await db.User.create(user, { logging: false })).dataValues; + } catch (error) { + throw error; + } + }); + + it('should notify a user when an article is published', async () => { + const createdNotification = await Notification.create(createdUser.id, notification.message); + expect(Object.keys(createdNotification).length).to.be.above(0); + expect(createdNotification).to.not.include.keys('errors'); + }); + + it('should not notify a user ID is not found', async () => { + const createdNotification = await Notification.create(0, notification.message); + expect(createdNotification).to.include.keys('errors'); + }); + + it('should not notify a user if the message is not provided', async () => { + const createdNotification = await Notification.create(createdUser.id); + expect(createdNotification).to.include.keys('errors'); + }); + + it('should not notify a user if no parameter is not provided', async () => { + const createdNotification = await Notification.create(); + expect(createdNotification).to.include.keys('errors'); + }); +}); diff --git a/src/tests/queries/notifications/getAllNotifications.test.js b/src/tests/queries/notifications/getAllNotifications.test.js new file mode 100644 index 00000000..354cc640 --- /dev/null +++ b/src/tests/queries/notifications/getAllNotifications.test.js @@ -0,0 +1,59 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import chai from 'chai'; +import db from '../../../models'; +import { Notification } from '../../../queries'; +import * as Factory from '../../../helpers/factory'; + +const { expect } = chai; + +let createdUser = ''; + +const user = Factory.user.build(); + +delete user.id; + +describe('Get notifications query', () => { + before(async () => { + try { + await db.User.destroy({ + truncate: true, + cascade: true, + logging: false + }); + createdUser = (await db.User.create(user, { logging: false })).dataValues; + await db.Notification.create(Factory.notification.build({ userId: createdUser.id }), { + logging: false + }); + } catch (error) { + throw error; + } + }); + + it('should get all notifications', async () => { + const notifications = await Notification.getAll(createdUser.id, 'unseen'); + expect(notifications.length).to.be.above(0); + expect(notifications).to.not.include.keys('errors'); + }); + + it('should get all notifications', async () => { + const notifications = await Notification.getAll(createdUser.id); + expect(notifications.length).to.be.above(0); + expect(notifications).to.not.include.keys('errors'); + }); + + it('should not get all notifications if the user ID is not found', async () => { + const notifications = await Notification.getAll(0, 'unseen'); + expect(notifications.length).to.be.equal(0); + expect(notifications).to.not.include.keys('errors'); + }); + + it('should not get all notifications if no provided parameter', async () => { + const notifications = await Notification.getAll(); + expect(notifications).to.include.keys('errors'); + }); + + it('should throw an error if the user ID is not valid', async () => { + const notifications = await Notification.getAll(['~~~']); + expect(notifications).to.include.keys('errors'); + }); +}); diff --git a/src/tests/queries/notifications/getOneNotification.test.js b/src/tests/queries/notifications/getOneNotification.test.js new file mode 100644 index 00000000..3cac201b --- /dev/null +++ b/src/tests/queries/notifications/getOneNotification.test.js @@ -0,0 +1,62 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import chai from 'chai'; +import db from '../../../models'; +import { Notification } from '../../../queries'; +import * as Factory from '../../../helpers/factory'; + +const { expect } = chai; + +let createdUser = ''; +let createdNotification = ''; + +const user = Factory.user.build(); + +delete user.id; + +describe('Get notification query', () => { + before(async () => { + try { + await db.User.destroy({ + truncate: true, + cascade: true, + logging: false + }); + createdUser = (await db.User.create(user, { logging: false })).dataValues; + createdNotification = (await db.Notification.create( + Factory.notification.build({ userId: createdUser.id }), + { + logging: false + } + )).dataValues; + } catch (error) { + throw error; + } + }); + + it('should get one notification', async () => { + const notification = await Notification.getOne(createdUser.id, createdNotification.id); + expect(Object.keys(notification).length).to.be.above(0); + expect(notification).to.not.include.keys('errors'); + }); + + it('should not get one notification if the notification ID is not provided', async () => { + const notification = await Notification.getOne(createdUser.id); + expect(notification).to.include.keys('errors'); + }); + + it('should not get one notification if the user ID is not found', async () => { + const notification = await Notification.getOne(0, createdNotification.id); + expect(Object.keys(notification).length).to.be.equal(0); + expect(notification).to.not.include.keys('errors'); + }); + + it('should not get one notification if no provided parameter', async () => { + const notification = await Notification.getOne(); + expect(notification).to.include.keys('errors'); + }); + + it('should throw an error if the user ID or notification ID is not valid', async () => { + const notification = await Notification.getOne(['~~~'], ['~~~']); + expect(notification).to.include.keys('errors'); + }); +}); diff --git a/src/tests/queries/notifications/index.js b/src/tests/queries/notifications/index.js new file mode 100644 index 00000000..3a6fda6a --- /dev/null +++ b/src/tests/queries/notifications/index.js @@ -0,0 +1,5 @@ +import './createNotification.test'; +import './getAllNotifications.test'; +import './getOneNotification.test'; +import './updateNotifications.test'; +import './removeNotification.test'; diff --git a/src/tests/queries/notifications/removeNotification.test.js b/src/tests/queries/notifications/removeNotification.test.js new file mode 100644 index 00000000..4b2b473b --- /dev/null +++ b/src/tests/queries/notifications/removeNotification.test.js @@ -0,0 +1,70 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import chai from 'chai'; +import db from '../../../models'; +import { Notification } from '../../../queries'; +import * as Factory from '../../../helpers/factory'; + +const { expect } = chai; + +let createdUser = ''; +let createdNotificationOne = ''; +let createdNotificationTwo = ''; +const user = Factory.user.build(); +const message = 'Hello'; + +delete user.id; + +describe('Remove notification query', () => { + before(async () => { + try { + await db.User.destroy({ + truncate: true, + cascade: true, + logging: false + }); + createdUser = (await db.User.create(user, { logging: false })).dataValues; + createdNotificationOne = (await db.Notification.create( + { userId: createdUser.id, message }, + { logging: false } + )).dataValues; + createdNotificationTwo = (await db.Notification.create( + { userId: createdUser.id, message }, + { logging: false } + )).dataValues; + } catch (error) { + throw error; + } + }); + + it('should remove a notification', async () => { + const removedNotification = await Notification.remove( + createdNotificationOne.id, + createdUser.id + ); + expect(removedNotification).to.be.above(0); + expect(removedNotification).to.not.include.keys('errors'); + }); + + it('should not remove a notification if the notification ID is not found', async () => { + const removedNotification = await Notification.remove(0, createdUser.id); + expect(removedNotification).to.be.equal(0); + }); + + it('should not remove a notification if the user ID is not found', async () => { + const removedNotification = await Notification.remove( + createdNotificationTwo.id, + createdUser.id * 2 + ); + expect(removedNotification).to.be.equal(0); + }); + + it('should not remove a notification if the parameter passed is invalid', async () => { + const removedNotification = await Notification.remove({}); + expect(removedNotification).to.include.keys('errors'); + }); + + it('should not remove a notification if there is no passed parameter', async () => { + const removedNotification = await Notification.remove(); + expect(removedNotification).to.include.keys('errors'); + }); +}); diff --git a/src/tests/queries/notifications/updateNotifications.test.js b/src/tests/queries/notifications/updateNotifications.test.js new file mode 100644 index 00000000..b4fafaf3 --- /dev/null +++ b/src/tests/queries/notifications/updateNotifications.test.js @@ -0,0 +1,82 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import chai from 'chai'; +import db from '../../../models'; +import { Notification } from '../../../queries'; +import * as Factory from '../../../helpers/factory'; + +const { expect } = chai; + +let createdUser = ''; +let createdNotification = ''; + +const user = Factory.user.build(); + +delete user.id; + +describe('Update notification query', () => { + before(async () => { + try { + await db.User.destroy({ + truncate: true, + cascade: true, + logging: false + }); + createdUser = (await db.User.create(user, { logging: false })).dataValues; + createdNotification = (await db.Notification.create( + Factory.notification.build({ userId: createdUser.id }), + { logging: false } + )).get(); + } catch (error) { + throw error; + } + }); + + it('should update one notification', async () => { + const notifications = await Notification.update(createdUser.id, createdNotification.id, { + status: 'seen' + }); + expect(notifications.length).to.be.above(0); + expect(notifications).to.not.include.keys('errors'); + }); + + it('should not update one notification if the notification ID is not found', async () => { + const notifications = await Notification.update(createdUser.id, createdNotification.id * 2, { + status: 'seen' + }); + expect(notifications.length).to.be.equal(0); + expect(notifications).to.not.include.keys('errors'); + }); + + it('should not update one notification if the user ID is not found', async () => { + const notifications = await Notification.update(0, createdNotification.id, { status: 'seen' }); + expect(notifications.length).to.be.equal(0); + expect(notifications).to.not.include.keys('errors'); + }); + + it('should update notifications of a given user', async () => { + const notifications = await Notification.update(createdUser.id, null, { status: 'seen' }); + expect(notifications.length).to.be.above(0); + expect(notifications).to.not.include.keys('errors'); + }); + + it('should update notifications of a given user if the status is not valid', async () => { + const notifications = await Notification.update(createdUser.id, null, { status: 'seennn' }); + expect(notifications).to.include.keys('errors'); + }); + + it('should not update notifications of a given user if the user ID is not found', async () => { + const notifications = await Notification.update(0, null, { status: 'seen' }); + expect(notifications.length).to.be.equal(0); + expect(notifications).to.not.include.keys('errors'); + }); + + it('should not update one notification if no provided parameter', async () => { + const notifications = await Notification.update(); + expect(notifications).to.include.keys('errors'); + }); + + it('should throw an error if the user ID or notification ID is not valid', async () => { + const notifications = await Notification.update(['~~~'], ['~~~']); + expect(notifications).to.include.keys('errors'); + }); +}); diff --git a/src/tests/queries/notificationsConfig/createNotificationConfig.test.js b/src/tests/queries/notificationsConfig/createNotificationConfig.test.js new file mode 100644 index 00000000..ef276ca2 --- /dev/null +++ b/src/tests/queries/notificationsConfig/createNotificationConfig.test.js @@ -0,0 +1,56 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import chai from 'chai'; +import db from '../../../models'; +import { Notification } from '../../../queries'; +import * as Factory from '../../../helpers/factory'; + +const { expect } = chai; + +let createdUser = ''; + +const user = Factory.user.build(); +const notificationConfig = Factory.notificationConfig.build(); + +delete user.id; + +describe('Create notification config query', () => { + before(async () => { + try { + await db.User.destroy({ + truncate: true, + cascade: true, + logging: false + }); + createdUser = (await db.User.create(user, { logging: false })).dataValues; + } catch (error) { + throw error; + } + }); + + it('should save a user configuration', async () => { + const createdNotificationConfig = await Notification.config.create( + createdUser.id, + notificationConfig.config + ); + expect(Object.keys(createdNotificationConfig).length).to.be.above(0); + expect(createdNotificationConfig).to.not.include.keys('errors'); + }); + + it('should not save a user configuration if it already exists', async () => { + const createdNotificationConfig = await Notification.config.create( + createdUser.id, + notificationConfig.config + ); + expect(createdNotificationConfig).to.include.keys('errors'); + }); + + it('should not save a user configuration if the configurations is not passed', async () => { + const createdNotificationConfig = await Notification.config.create(createdUser.id); + expect(createdNotificationConfig).to.include.keys('errors'); + }); + + it('should not save a user configuration if no parameter is provided', async () => { + const createdNotificationConfig = await Notification.config.create(); + expect(createdNotificationConfig).to.include.keys('errors'); + }); +}); diff --git a/src/tests/queries/notificationsConfig/getOneNotificationConfig.test.js b/src/tests/queries/notificationsConfig/getOneNotificationConfig.test.js new file mode 100644 index 00000000..a23ddffd --- /dev/null +++ b/src/tests/queries/notificationsConfig/getOneNotificationConfig.test.js @@ -0,0 +1,52 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import chai from 'chai'; +import db from '../../../models'; +import { Notification } from '../../../queries'; +import * as Factory from '../../../helpers/factory'; + +const { expect } = chai; + +let createdUser = ''; + +const user = Factory.user.build(); +const notificationConfig = Factory.notificationConfig.build(); + +delete user.id; + +describe('Get notification config query', () => { + before(async () => { + try { + await db.User.destroy({ + truncate: true, + cascade: true, + logging: false + }); + createdUser = (await db.User.create(user, { logging: false })).dataValues; + notificationConfig.userId = createdUser.id; + notificationConfig.config = JSON.stringify(notificationConfig.config); + await db.NotificationConfig.create(notificationConfig, { logging: false }); + } catch (error) { + throw error; + } + }); + + it('should get one configuration', async () => { + const savedConfig = await Notification.config.getOne(createdUser.id); + expect(Object.keys(savedConfig).length).to.be.above(0); + }); + + it('should not get one configuration if the user ID is not found', async () => { + const savedConfig = await Notification.config.getOne(0); + expect(Object.keys(savedConfig).length).to.be.equal(0); + }); + + it('should not throw an error if no provided parameter', async () => { + const savedConfig = await Notification.config.getOne(); + expect(savedConfig).to.include.keys('errors'); + }); + + it('should not throw an error if the user ID is not valid', async () => { + const savedConfig = await Notification.config.getOne(['~~~']); + expect(savedConfig).to.include.keys('errors'); + }); +}); diff --git a/src/tests/queries/notificationsConfig/index.js b/src/tests/queries/notificationsConfig/index.js new file mode 100644 index 00000000..d4ca60f1 --- /dev/null +++ b/src/tests/queries/notificationsConfig/index.js @@ -0,0 +1,3 @@ +import './createNotificationConfig.test'; +import './getOneNotificationConfig.test'; +import './updateNotificationConfig.test'; diff --git a/src/tests/queries/notificationsConfig/updateNotificationConfig.test.js b/src/tests/queries/notificationsConfig/updateNotificationConfig.test.js new file mode 100644 index 00000000..9b8f560f --- /dev/null +++ b/src/tests/queries/notificationsConfig/updateNotificationConfig.test.js @@ -0,0 +1,56 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import chai from 'chai'; +import db from '../../../models'; +import { Notification } from '../../../queries'; +import * as Factory from '../../../helpers/factory'; + +const { expect } = chai; + +let createdUser = ''; +let createdConfig = ''; +const user = Factory.user.build(); +const notificationConfig = Factory.notificationConfig.build(); + +delete user.id; + +describe('Update notification config query', () => { + before(async () => { + try { + await db.User.destroy({ + truncate: true, + cascade: true, + logging: false + }); + createdUser = (await db.User.create(user, { logging: false })).dataValues; + notificationConfig.userId = createdUser.id; + notificationConfig.config = JSON.stringify(notificationConfig.config); + createdConfig = (await db.NotificationConfig.create(notificationConfig, { logging: false })) + .dataValues; + } catch (error) { + throw error; + } + }); + + it('should update the configuration', async () => { + notificationConfig.config = JSON.parse(notificationConfig.config); + notificationConfig.config.email.articles.send = false; + const updatedConfig = await Notification.config.update(createdUser.id, notificationConfig); + expect(Object.keys(updatedConfig).length).to.be.above(0); + expect(updatedConfig).to.not.include.keys('errors'); + }); + + it('should not update the configuration if the user ID is not found', async () => { + const updatedConfig = await Notification.config.update(0, notificationConfig); + expect(Object.keys(updatedConfig).length).to.be.equal(0); + }); + + it('should not update the configuration if no provided parameter', async () => { + const updatedConfig = await Notification.config.update(); + expect(Object.keys(updatedConfig).length).to.be.equal(0); + }); + + it('should not throw an error if the user ID is not valid', async () => { + const updatedConfig = await Notification.config.update(notificationConfig, ['~~~']); + expect(updatedConfig).to.include.keys('errors'); + }); +}); diff --git a/src/tests/queries/permissions/createPermission.test.js b/src/tests/queries/permissions/createPermission.test.js new file mode 100644 index 00000000..ad36eb42 --- /dev/null +++ b/src/tests/queries/permissions/createPermission.test.js @@ -0,0 +1,38 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import chai from 'chai'; +import db from '../../../models'; +import { User } from '../../../queries'; +import * as Factory from '../../../helpers/factory'; + +const { expect } = chai; + +const { permissions } = Factory.permissionsNormal.build(); +describe('Find or create permissions query', () => { + before(async () => { + try { + await db.Permission.destroy({ + truncate: true, + cascade: true, + logging: false + }); + } catch (error) { + throw error; + } + }); + + it('should throw an error', async () => { + const newPermissions = await User.permissions.create(); + expect(newPermissions).to.include.keys('errors'); + }); + + it('should create permissions', async () => { + const newPermissions = await User.permissions.create('normal', permissions); + expect(Object.keys(newPermissions).length).to.be.above(0); + expect(newPermissions).to.not.include.keys('errors'); + }); + + it('should not create permissions if it is already created', async () => { + const newPermissions = await User.permissions.create('normal', permissions); + expect(newPermissions).to.include.keys('errors'); + }); +}); diff --git a/src/tests/queries/permissions/findOrCreatePermission.test.js b/src/tests/queries/permissions/findOrCreatePermission.test.js new file mode 100644 index 00000000..50cffeed --- /dev/null +++ b/src/tests/queries/permissions/findOrCreatePermission.test.js @@ -0,0 +1,39 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import chai from 'chai'; +import db from '../../../models'; +import { User } from '../../../queries'; +import * as Factory from '../../../helpers/factory'; + +const { expect } = chai; + +const { permissions } = Factory.permissionsNormal.build(); +describe('Find or create permissions query', () => { + before(async () => { + try { + await db.Permission.destroy({ + truncate: true, + cascade: true, + logging: false + }); + } catch (error) { + throw error; + } + }); + + it('should throw an error', async () => { + const newOrExistingPermissions = await User.permissions.findOrCreate(); + expect(newOrExistingPermissions).to.include.keys('errors'); + }); + + it('should create permissions', async () => { + const newOrExistingPermissions = await User.permissions.findOrCreate('normal', permissions); + expect(Object.keys(newOrExistingPermissions[0]).length).to.be.above(0); + expect(newOrExistingPermissions[1]).to.be.equal(true); + }); + + it('should not create permissions if it is already created', async () => { + const newOrExistingPermissions = await User.permissions.findOrCreate('normal', permissions); + expect(Object.keys(newOrExistingPermissions[0]).length).to.be.above(0); + expect(newOrExistingPermissions[1]).to.be.equal(false); + }); +}); diff --git a/src/tests/queries/ratings/create.test.js b/src/tests/queries/ratings/create.test.js new file mode 100644 index 00000000..aa08596e --- /dev/null +++ b/src/tests/queries/ratings/create.test.js @@ -0,0 +1,57 @@ +import chai from 'chai'; +import db from '../../../models'; +import { Article } from '../../../queries'; +import * as Factory from '../../../helpers/factory'; + +const { expect } = chai; +chai.should(); + +let createdArticle = ''; +let userId = ''; +const RATING = 3; +let createdUser = ''; +let article = Factory.article.build(); +const user = Factory.user.build(); +delete article.id; +delete article.tagList; +delete user.id; +user.email = 'ratingquery@haven.com'; +user.username = 'ratingquery123'; +article.slug = 'rosie-make-it-easy-gfkjfh1242'; +article.status = 'published'; + +describe('Create rating query', () => { + before(async () => { + createdUser = (await db.User.create(user, { logging: false })).dataValues; + userId = createdUser.id; + article = { ...article, userId }; + createdArticle = await db.Article.create(article, { logging: false }); + return createdArticle; + }); + + it('Should create rating', async () => { + const data = { + rating: RATING, + articleId: createdArticle.dataValues.id, + userId + }; + const savedRating = await Article.rate.create(data); + expect(Object.keys(savedRating).length).to.be.above(0); + expect(savedRating).to.not.include.keys('errors'); + }); + + it('Should not create or update rating', async () => { + const fakeData = { + rating: 1, + articleId: 12312, + userId: 143234345464 + }; + const savedRating = await Article.rate.create(fakeData); + expect(savedRating).to.include.keys('errors'); + }); + + it('Should not create or update rating', async () => { + const savedRating = await Article.rate.create(); + expect(savedRating).to.include.keys('errors'); + }); +}); diff --git a/src/tests/queries/ratings/get.test.js b/src/tests/queries/ratings/get.test.js new file mode 100644 index 00000000..8fbd44c5 --- /dev/null +++ b/src/tests/queries/ratings/get.test.js @@ -0,0 +1,28 @@ +import chai from 'chai'; +import db from '../../../models'; +import { Article } from '../../../queries'; +import * as helpers from '../../../helpers'; + +const { expect } = chai; + +let createdArticle = ''; +let userId = ''; +let article = helpers.factory.article.build(); +delete article.id; +delete article.tagList; +article.rating = 4; +article.status = 'published'; + +describe('Get rating query', () => { + before(async () => { + const response = await db.User.findAll({ limit: 1, attributes: ['id'], logging: false }); + userId = response[0].dataValues.id; + article = { ...article, userId, slug: helpers.generator.slug(article.slug) }; + createdArticle = await db.Article.create(article, { logging: false }); + return createdArticle; + }); + it('Should get users who rated an article', async () => { + const ratedArticle = await Article.rate.get(); + expect(Object.keys(ratedArticle).length).to.be.above(0); + }); +}); diff --git a/src/tests/queries/ratings/index.js b/src/tests/queries/ratings/index.js new file mode 100644 index 00000000..73a1db4c --- /dev/null +++ b/src/tests/queries/ratings/index.js @@ -0,0 +1,2 @@ +import './create.test'; +import './get.test'; diff --git a/src/tests/queries/tags/delete.test.js b/src/tests/queries/tags/delete.test.js new file mode 100644 index 00000000..f77d659d --- /dev/null +++ b/src/tests/queries/tags/delete.test.js @@ -0,0 +1,24 @@ +// eslint-disable-next-line import/no-extraneous-dependencies +import chai from 'chai'; +// eslint-disable-next-line import/no-extraneous-dependencies +import chaiHttp from 'chai-http'; +import { Tag } from '../../../queries'; +import * as Factory from '../../../helpers/factory'; +import db from '../../../models'; + +const article = Factory.article.build(); +delete article.id; +chai.use(chaiHttp); +describe('Tag query', () => { + it('should update tag', async () => { + const action = 'detele'; + const findArticle = await db.Article.findAll({ limit: 1, logging: false }); + const response = await Tag.update( + findArticle[0].dataValues.tagList, + findArticle[0].dataValues.slug, + action + ); + response.should.be.an('string'); + response.should.equal('Tag has been deleted'); + }); +}); diff --git a/src/tests/queries/tags/get.test.js b/src/tests/queries/tags/get.test.js new file mode 100644 index 00000000..4d04a953 --- /dev/null +++ b/src/tests/queries/tags/get.test.js @@ -0,0 +1,18 @@ +// eslint-disable-next-line import/no-extraneous-dependencies +import chai from 'chai'; +// eslint-disable-next-line import/no-extraneous-dependencies +import chaiHttp from 'chai-http'; +import { get } from '../../../queries/articles'; +import * as Factory from '../../../helpers/factory'; + +const { expect } = chai; + +const article = Factory.article.build(); +delete article.id; +chai.use(chaiHttp); +describe('Query to get tags', () => { + it('should get one article', async () => { + // const newArticle = await get({ slug: article.slug }); + // expect(Object.keys(newArticle).length).to.be.above(0); + }); +}); diff --git a/src/tests/queries/tags/index.js b/src/tests/queries/tags/index.js new file mode 100644 index 00000000..4b745247 --- /dev/null +++ b/src/tests/queries/tags/index.js @@ -0,0 +1,3 @@ +import './get.test'; +import './update.test'; +import './delete.test'; diff --git a/src/tests/queries/tags/update.test.js b/src/tests/queries/tags/update.test.js new file mode 100644 index 00000000..d2a013f7 --- /dev/null +++ b/src/tests/queries/tags/update.test.js @@ -0,0 +1,32 @@ +// eslint-disable-next-line import/no-extraneous-dependencies +import chai from 'chai'; +// eslint-disable-next-line import/no-extraneous-dependencies +import chaiHttp from 'chai-http'; +import { Tag, Article } from '../../../queries'; +import * as Factory from '../../../helpers/factory'; +import db from '../../../models'; + +const article = Factory.article.build(); +delete article.id; +const tagList = ['Morning', 'Bonjour', 'Bonjourno', 'Dias', 'Hakuna Matata']; +chai.use(chaiHttp); +describe('Tag query', () => { + let fetchArticle; + before(async () => { + fetchArticle = await db.Article.findAll({ limit: 1, logging: false }); + return fetchArticle; + }); + it('should update tag', async () => { + const action = 'update'; + const findArticle = await db.Article.findAll({ limit: 1, logging: false }); + const response = await Tag.update(article.tagList, findArticle[0].dataValues.slug, action); + response.should.be.an('string'); + response.should.equal('Tag list has been updated'); + }); + it('should not create tags if they are more than 5', async () => { + const action = 'update'; + const { slug } = fetchArticle[0].dataValues; + const updateTags = await Tag.update(tagList, slug, action); + updateTags.should.be.an('string'); + }); +}); diff --git a/src/tests/queries/tokens/destroyToken.test.js b/src/tests/queries/tokens/destroyToken.test.js new file mode 100644 index 00000000..f85b57ba --- /dev/null +++ b/src/tests/queries/tokens/destroyToken.test.js @@ -0,0 +1,53 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import chai from 'chai'; +import db from '../../../models'; +import { Token } from '../../../queries'; +import * as Factory from '../../../helpers/factory'; + +const { expect } = chai; + +let createdUser = ''; +let createdToken = ''; +const user = Factory.user.build(); +const { token } = Factory.token.build(); + +delete user.id; + +describe('Destroy token query', () => { + before(async () => { + try { + await db.User.destroy({ + truncate: true, + cascade: true, + logging: false + }); + createdUser = (await db.User.create(user, { logging: false })).dataValues; + createdToken = await db.Token.create( + { + token, + userId: createdUser.id, + createdAt: new Date(new Date() - 24 * 60 * 60 * 1000) + }, + { logging: true } + ); + } catch (error) { + throw error; + } + }); + + it('should destroy a token', async () => { + const destroyedToken = await Token.destroy(createdUser.id); + expect(destroyedToken).to.be.above(0); + expect(destroyedToken).to.not.include.keys('errors'); + }); + + it('should not destroy a token if no expired token found', async () => { + const destroyedToken = await Token.destroy(createdUser.id); + expect(destroyedToken).to.be.equal(0); + }); + + it('should throw an error if the parameter is not a integer', async () => { + const destroyedToken = await Token.destroy({}); + expect(destroyedToken).to.include.keys('errors'); + }); +}); diff --git a/src/tests/queries/tokens/findOneToken.test.js b/src/tests/queries/tokens/findOneToken.test.js new file mode 100644 index 00000000..e2f9c85c --- /dev/null +++ b/src/tests/queries/tokens/findOneToken.test.js @@ -0,0 +1,51 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import chai from 'chai'; +import db from '../../../models'; +import { Token } from '../../../queries'; +import * as Factory from '../../../helpers/factory'; + +const { expect } = chai; + +let createdUser = ''; +const user = Factory.user.build(); +const { token } = Factory.token.build(); + +delete user.id; + +describe('Update token query', () => { + before(async () => { + try { + await db.User.destroy({ + truncate: true, + cascade: true, + logging: false + }); + createdUser = (await db.User.create(user, { logging: false })).dataValues; + await db.Token.create({ token, userId: createdUser.id }, { logging: false }); + } catch (error) { + throw error; + } + }); + + it('should find a token', async () => { + const foundToken = await Token.findOne(createdUser.id, token); + expect(Object.keys(foundToken).length).to.be.above(0); + expect(foundToken).to.not.include.keys('errors'); + }); + + it('should not find a token if the token or the user ID is not provided', async () => { + const foundToken = await Token.findOne(0, token); + expect(Object.keys(foundToken).length).to.be.equal(0); + expect(foundToken).to.not.include.keys('errors'); + }); + + it('should not find a token if the parameter passed are invalid', async () => { + const foundToken = await Token.findOne({}); + expect(foundToken).to.include.keys('errors'); + }); + + it('should an empty object if there is no parameter passed', async () => { + const foundToken = await Token.findOne(); + expect(Object.keys(foundToken).length).to.be.equal(0); + }); +}); diff --git a/src/tests/queries/tokens/index.js b/src/tests/queries/tokens/index.js new file mode 100644 index 00000000..cda071c1 --- /dev/null +++ b/src/tests/queries/tokens/index.js @@ -0,0 +1,3 @@ +import './saveToken.test'; +import './findOneToken.test'; +import './destroyToken.test'; diff --git a/src/tests/queries/tokens/saveToken.test.js b/src/tests/queries/tokens/saveToken.test.js new file mode 100644 index 00000000..fd3de604 --- /dev/null +++ b/src/tests/queries/tokens/saveToken.test.js @@ -0,0 +1,49 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import chai from 'chai'; +import db from '../../../models'; +import { Token } from '../../../queries'; +import * as Factory from '../../../helpers/factory'; + +const { expect } = chai; + +let createdUser = ''; +const user = Factory.user.build(); +const { token } = Factory.token.build(); + +delete user.id; + +describe('Create token query', () => { + before(async () => { + try { + await db.User.destroy({ + truncate: true, + cascade: true, + logging: false + }); + createdUser = (await db.User.create(user, { logging: false })).dataValues; + } catch (error) { + throw error; + } + }); + + it('should save a token', async () => { + const savedToken = await Token.save(token, createdUser.id); + expect(Object.keys(savedToken).length).to.be.above(0); + expect(savedToken).to.not.include.keys('errors'); + }); + + it('should not save a token if the user ID is not provided', async () => { + const savedToken = await Token.save(token); + expect(savedToken).to.include.keys('errors'); + }); + + it('should not save a token if the parameter passed is invalid', async () => { + const savedToken = await Token.save('~~~'); + expect(savedToken).to.include.keys('errors'); + }); + + it('should not save a token if there is no passed parameter', async () => { + const savedToken = await Token.save(); + expect(savedToken).to.include.keys('errors'); + }); +}); diff --git a/src/tests/queries/users/createUser.test.js b/src/tests/queries/users/createUser.test.js new file mode 100644 index 00000000..eaa0ec1b --- /dev/null +++ b/src/tests/queries/users/createUser.test.js @@ -0,0 +1,57 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import chai from 'chai'; +import db from '../../../models'; +import { User } from '../../../queries'; +import * as Factory from '../../../helpers/factory'; + +const { expect } = chai; + +const user = Factory.user.build(); +delete user.id; + +describe('Create user query', () => { + before(async () => { + try { + user.email = Factory.user.build().email; + await db.User.destroy({ + truncate: true, + cascade: true, + logging: false + }); + } catch (error) { + throw error; + } + }); + + it('should create a user account', async () => { + const newUser = await User.create(user); + expect(Object.keys(newUser).length).to.be.above(0); + }); + + it('should not create a user account', async () => { + const newUser = await User.create({}); + expect(newUser).to.include.keys('errors'); + }); + + it('should throw an error message', async () => { + const newUser = await User.create('~~~'); + expect(newUser).to.include.keys('errors'); + }); + + it('should throw an error message if there is no passed parameter', async () => { + const newUser = await User.create(); + expect(newUser).to.include.keys('errors'); + }); + + after(async () => { + try { + user.email = Factory.user.build().email; + await db.User.destroy({ + where: { email: user.email }, + logging: false + }); + } catch (error) { + throw error; + } + }); +}); diff --git a/src/tests/queries/users/findOneUser.test.js b/src/tests/queries/users/findOneUser.test.js new file mode 100644 index 00000000..5b15d584 --- /dev/null +++ b/src/tests/queries/users/findOneUser.test.js @@ -0,0 +1,57 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import chai from 'chai'; +import db from '../../../models'; +import { User } from '../../../queries'; +import * as Factory from '../../../helpers/factory'; + +const { expect } = chai; + +const newUser = Factory.user.build(); +delete newUser.id; + +describe('Find user query', () => { + before(async () => { + try { + await db.User.destroy({ + truncate: true, + cascade: true, + logging: false + }); + await db.User.create(newUser, { logging: false }); + } catch (error) { + throw error; + } + }); + + it('should return the user information', async () => { + const findUser = await User.findOne({ email: newUser.email }); + expect(Object.keys(findUser).length).to.be.above(0); + }); + + it('should not return the user information', async () => { + const findUser = await User.findOne({ email: 'xxxxx@gmail.com' }); + expect(Object.keys(findUser).length).to.be.equals(0); + }); + + it('should throw an error message', async () => { + const findUser = await User.findOne('~~'); + expect(findUser).to.include.keys('errors'); + }); + + it('should an empty object if there is no parameter passed', async () => { + const findUser = await User.findOne(); + expect(Object.keys(findUser).length).to.be.equal(0); + }); + + after(async () => { + try { + newUser.email = Factory.user.build().email; + await db.User.destroy({ + where: { email: newUser.email }, + logging: false + }); + } catch (error) { + throw error; + } + }); +}); diff --git a/src/tests/queries/users/findOrCreateUser.test.js b/src/tests/queries/users/findOrCreateUser.test.js new file mode 100644 index 00000000..062c0353 --- /dev/null +++ b/src/tests/queries/users/findOrCreateUser.test.js @@ -0,0 +1,53 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import chai from 'chai'; +import db from '../../../models'; +import { User } from '../../../queries'; +import * as Factory from '../../../helpers/factory'; + +const { expect } = chai; + +const user = Factory.user.build(); +delete user.id; + +describe('Find or create user query', () => { + before(async () => { + try { + await db.User.destroy({ + truncate: true, + cascade: true, + logging: false + }); + } catch (error) { + throw error; + } + }); + + it('should throw an error message', async () => { + const newOrExistingUser = await User.findOrCreate(); + expect(newOrExistingUser).to.include.keys('errors'); + }); + + it('should create a user account', async () => { + const newOrExistingUser = await User.findOrCreate({ email: user.email }, user); + expect(Object.keys(newOrExistingUser[0]).length).to.be.above(0); + expect(newOrExistingUser[1]).to.be.equal(true); + }); + + it('should not create a user account', async () => { + const newOrExistingUser = await User.findOrCreate({ email: user.email }, user); + expect(Object.keys(newOrExistingUser[0]).length).to.be.above(0); + expect(newOrExistingUser[1]).to.be.equal(false); + }); + + after(async () => { + try { + user.email = Factory.user.build().email; + await db.User.destroy({ + where: { email: user.email }, + logging: false + }); + } catch (error) { + throw error; + } + }); +}); diff --git a/src/tests/queries/users/followUser.test.js b/src/tests/queries/users/followUser.test.js new file mode 100644 index 00000000..eb494f6e --- /dev/null +++ b/src/tests/queries/users/followUser.test.js @@ -0,0 +1,15 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import chai from 'chai'; +import { User } from '../../../queries'; + +const { expect } = chai; + +describe('Find follow query', () => { + it('should fail ', async () => { + const Follow = await User.follow.add({ + following: 1, + userId: 'req.user.id' + }); + expect(Object.keys(Follow).length).to.be.above(0); + }); +}); diff --git a/src/tests/queries/users/index.js b/src/tests/queries/users/index.js new file mode 100644 index 00000000..4f7b914a --- /dev/null +++ b/src/tests/queries/users/index.js @@ -0,0 +1,6 @@ +import './createUser.test'; +import './findOneUser.test'; +import './updateUser.test'; +import './findOrCreateUser.test'; +import './followUser.test'; +import './unFollowUser.test'; diff --git a/src/tests/queries/users/unFollowUser.test.js b/src/tests/queries/users/unFollowUser.test.js new file mode 100644 index 00000000..c0ca279c --- /dev/null +++ b/src/tests/queries/users/unFollowUser.test.js @@ -0,0 +1,12 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import chai from 'chai'; +import { User } from '../../../queries'; + +const { expect } = chai; + +describe('Find follow query', () => { + it('should fail ', async () => { + const Follow = await User.follow.remove({ userId: 0, following: '-' }); + expect(Object.keys(Follow).length).to.be.above(0); + }); +}); diff --git a/src/tests/queries/users/updateUser.test.js b/src/tests/queries/users/updateUser.test.js new file mode 100644 index 00000000..216c4a0b --- /dev/null +++ b/src/tests/queries/users/updateUser.test.js @@ -0,0 +1,45 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import chai from 'chai'; +import db from '../../../models'; +import { User } from '../../../queries'; +import * as Factory from '../../../helpers/factory'; + +const { expect } = chai; + +const user = Factory.user.build(); +delete user.id; + +describe('Update user query', () => { + before(async () => { + try { + user.email = Factory.user.build().email; + await db.User.destroy({ + where: { email: user.email }, + logging: false + }); + await db.User.create(user, { logging: false }); + } catch (error) { + throw error; + } + }); + + it('should update user information', async () => { + const updatedUser = await User.update({ password: '12345' }, { email: user.email }); + expect(Object.keys(updatedUser).length).to.be.above(0); + }); + + it('should not update a user account', async () => { + const updatedUser = await User.update({ password: '12345' }, { email: 'aaa' }); + expect(Object.keys(updatedUser).length).to.be.equal(0); + }); + + it('should throw an error message', async () => { + const updatedUser = await User.update({}, '~~'); + expect(updatedUser).to.include.keys('errors'); + }); + + it('should throw an error message if no parameter is passed', async () => { + const updatedUser = await User.update(); + expect(Object.keys(updatedUser).length).to.be.equal(0); + }); +}); diff --git a/src/tests/routes/404.test.js b/src/tests/routes/404.test.js new file mode 100644 index 00000000..7c4b2444 --- /dev/null +++ b/src/tests/routes/404.test.js @@ -0,0 +1,19 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import chai from 'chai'; +import chaiHttp from 'chai-http'; +import app from '../../app'; + +const { expect } = chai; + +chai.use(chaiHttp); + +describe('POST /apiii', () => { + it('should return an error message', () => { + chai + .request(app) + .get('/apiii') + .end((err, res) => { + expect(res.status).to.equal(404); + }); + }); +}); diff --git a/src/tests/routes/allUser.test.js b/src/tests/routes/allUser.test.js new file mode 100644 index 00000000..8ba84a8a --- /dev/null +++ b/src/tests/routes/allUser.test.js @@ -0,0 +1,58 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import chai from 'chai'; +import jwt from 'jsonwebtoken'; +import dotenv from 'dotenv'; +import db from '../../models'; +import status from '../../config/status'; +import * as Factory from '../../helpers/factory'; +import app from '../../app'; + +dotenv.config(); + +let accessToken; +let createdUserOne = {}; + +const userOne = Factory.user.build(); +const userTwo = Factory.user.build(); + +delete userOne.id; +delete userTwo.id; + +userTwo.email = 'email@gmail.ccom'; +userTwo.username = 'newuser'; +userTwo.role = 'normal'; + +describe('All users', () => { + before(async () => { + try { + await db.User.destroy({ + truncate: true, + cascade: true, + logging: false + }); + createdUserOne = (await db.User.create(userOne, { logging: false })).dataValues; + await db.User.create(userTwo, { logging: false }); + accessToken = jwt.sign( + { + id: createdUserOne.id, + permissions: createdUserOne.permissions + }, + process.env.SECRET_KEY, + { expiresIn: '1d' } + ); + } catch (error) { + throw error; + } + }); + + it('should let a user see all authors', (done) => { + chai + .request(app) + .get('/api/v1/users/authors') + .set('access-token', accessToken) + .end((err, res) => { + res.should.have.status(status.OK); + done(); + }); + }); +}); diff --git a/src/tests/routes/articleLike.test.js b/src/tests/routes/articleLike.test.js new file mode 100644 index 00000000..8f0295d5 --- /dev/null +++ b/src/tests/routes/articleLike.test.js @@ -0,0 +1,157 @@ +/* eslint-disable no-undef */ +/* eslint-disable import/no-extraneous-dependencies */ +import chai from 'chai'; +import chaiHttp from 'chai-http'; +import jwt from 'jsonwebtoken'; +import * as Factory from '../../helpers/factory'; +import status from '../../config/status'; +import db from '../../models'; +import app from '../../app'; + +chai.use(chaiHttp); +chai.should(); +let accessToken; +let createdUser = {}; +let createdArticle = {}; +let newarticleSlug; + +const newUser = Factory.user.build(); +const newArticle = Factory.article.build(); + +delete newUser.id; +delete newArticle.id; + +describe('Article likes', () => { + before(async () => { + try { + await db.User.destroy({ + truncate: true, + cascade: true, + logging: false + }); + await db.Article.destroy({ + truncate: true, + cascade: true, + logging: false + }); + + await db.ArticleLike.destroy({ + truncate: true, + cascade: true, + logging: false + }); + + createdUser = (await db.User.create(newUser, { logging: false })).dataValues; + accessToken = jwt.sign( + { id: createdUser.id, role: createdUser.role, permissions: createdUser.permissions }, + process.env.SECRET_KEY, + { expiresIn: '1d' } + ); + newArticle.userId = createdUser.id; + createdArticle = (await db.Article.create(newArticle, { logging: false })).dataValues; + newarticleSlug = createdArticle.slug; + } catch (err) { + throw err; + } + }); + it('Should let the user dislike an article ', (done) => { + chai + .request(app) + .post(`/api/v1/articles/${newarticleSlug}/dislike`) + .set('access-token', accessToken) + .send() + .end((err, res) => { + res.should.have.status(status.CREATED); + done(); + }); + }); + it('Should not let the user dislike an article more than once', (done) => { + chai + .request(app) + .post(`/api/v1/articles/${newarticleSlug}/dislike`) + .set('access-token', accessToken) + .send() + .end((err, res) => { + res.should.have.status(status.OK); + done(); + }); + }); + it('Should let the user like an article', (done) => { + chai + .request(app) + .post(`/api/v1/articles/${newarticleSlug}/like`) + .set('access-token', accessToken) + .send() + .end((err, res) => { + res.should.have.status(status.CREATED); + done(); + }); + }); + it('Should not let the user like an article more than once', (done) => { + chai + .request(app) + .post(`/api/v1/articles/${newarticleSlug}/like`) + .set('access-token', accessToken) + .send() + .end((err, res) => { + res.should.have.status(status.OK); + done(); + }); + }); + + it('Should let the user like an article once he deleted the like', (done) => { + chai + .request(app) + .post(`/api/v1/articles/${newarticleSlug}/like`) + .set('access-token', accessToken) + .send() + .end((err, res) => { + res.should.have.status(status.CREATED); + done(); + }); + }); + it('Should let the user dislike an article', (done) => { + chai + .request(app) + .post(`/api/v1/articles/${newarticleSlug}/dislike`) + .set('access-token', accessToken) + .send() + .end((err, res) => { + res.should.have.status(status.OK); + done(); + }); + }); + it('Should let a user see who disliked on article ', (done) => { + chai + .request(app) + .get(`/api/v1/articles/${newarticleSlug}/likes`) + .set('access-token', accessToken) + .send() + .end((err, res) => { + res.should.have.status(status.OK); + done(); + }); + }); + it('Should let the user change their reaction on an article ', (done) => { + chai + .request(app) + .post(`/api/v1/articles/${newarticleSlug}/like`) + .set('access-token', accessToken) + .send() + .end((err, res) => { + res.should.have.status(status.OK); + done(); + }); + }); + it('Should let a user see who liked on article ', (done) => { + chai + .request(app) + .get(`/api/v1/articles/${newarticleSlug}/likes`) + .set('access-token', accessToken) + .send() + .end((err, res) => { + res.should.have.status(status.OK); + done(); + }); + }); +}); diff --git a/src/tests/routes/articles.test.js b/src/tests/routes/articles.test.js new file mode 100644 index 00000000..53ed7fcb --- /dev/null +++ b/src/tests/routes/articles.test.js @@ -0,0 +1,417 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import chai from 'chai'; +import jwt from 'jsonwebtoken'; +import dotenv from 'dotenv'; +import db from '../../models'; +import status from '../../config/status'; +import * as Factory from '../../helpers/factory'; +import app from '../../app'; + +dotenv.config(); + +const { expect } = chai; + +let accessToken = ''; +let createdUser = ''; +let createdArticle = ''; + +const user = Factory.user.build(); +const article = Factory.article.build(); + +delete user.id; +delete article.id; + +describe('Bookmark article', () => { + before(async () => { + try { + await db.User.destroy({ + truncate: true, + cascade: true, + logging: false + }); + createdUser = (await db.User.create(user, { logging: false })).dataValues; + article.userId = createdUser.id; + accessToken = jwt.sign( + { id: createdUser.id, role: createdUser.role, permissions: createdUser.permissions }, + process.env.SECRET_KEY, + { expiresIn: '1d' } + ); + createdArticle = (await db.Article.create(article, { logging: false })).dataValues; + } catch (error) { + throw error; + } + }); + + // bookmark an article + it('should bookmark an article', (done) => { + chai + .request(app) + .patch(`/api/v1/articles/${createdArticle.slug}/bookmark`) + .set('access-token', accessToken) + .end((err, res) => { + expect(res.status).to.be.equal(status.CREATED); + expect(res.body).to.not.include.keys('errors'); + done(); + }); + }); + + it('should not bookmark an article if it has already been bookmarked by the same user', (done) => { + chai + .request(app) + .patch(`/api/v1/articles/${createdArticle.slug}/bookmark`) + .set('access-token', accessToken) + .end((err, res) => { + expect(res.status).to.be.equal(status.EXIST); + expect(res.body).to.include.keys('errors'); + done(); + }); + }); + + it('should not bookmark an article if the user is not authenticated', (done) => { + chai + .request(app) + .patch(`/api/v1/articles/${createdArticle.slug}/bookmark`) + .end((err, res) => { + expect(res.status).to.be.equal(status.UNAUTHORIZED); + expect(res.body).to.include.keys('errors'); + done(); + }); + }); + + it("should not bookmark an article if the user doesn't exist", (done) => { + const invalidUserAccessToken = jwt.sign( + { id: 0, role: createdUser.role, permissions: createdUser.permissions }, + process.env.SECRET_KEY, + { expiresIn: '1d' } + ); + chai + .request(app) + .patch(`/api/v1/articles/${createdArticle.slug}/bookmark`) + .set('access-token', invalidUserAccessToken) + .end((err, res) => { + expect(res.status).to.be.equal(status.UNAUTHORIZED); + expect(res.body).to.include.keys('errors'); + done(); + }); + }); + + it('should throw an error when bookmarking an article', (done) => { + const invalidUserAccessToken = jwt.sign( + { id: {}, role: createdUser.role, permissions: createdUser.permissions }, + process.env.SECRET_KEY, + { expiresIn: '1d' } + ); + chai + .request(app) + .patch(`/api/v1/articles/${createdArticle.slug}/bookmark`) + .set('access-token', invalidUserAccessToken) + .end((err, res) => { + expect(res.status).to.be.equal(status.SERVER_ERROR); + expect(res.body).to.include.keys('errors'); + done(); + }); + }); + + // get bookmarked articles + it('should get all bookmarked articles', (done) => { + chai + .request(app) + .get('/api/v1/articles/bookmarked') + .set('access-token', accessToken) + .end((err, res) => { + expect(res.status).to.be.equal(status.OK); + expect(res.body).to.not.include.keys('errors'); + done(); + }); + }); + + it('should not get all bookmarked articles if the user is not authenticated', (done) => { + chai + .request(app) + .get('/api/v1/articles/bookmarked') + .end((err, res) => { + expect(res.status).to.be.equal(status.UNAUTHORIZED); + expect(res.body).to.include.keys('errors'); + done(); + }); + }); + + it('should throw an error when getting bookmarked articles', (done) => { + const invalidUserAccessToken = jwt.sign( + { id: {}, role: createdUser.role, permissions: createdUser.permissions }, + process.env.SECRET_KEY, + { expiresIn: '1d' } + ); + chai + .request(app) + .get('/api/v1/articles/bookmarked') + .set('access-token', invalidUserAccessToken) + .end((err, res) => { + expect(res.status).to.be.equal(status.SERVER_ERROR); + expect(res.body).to.include.keys('errors'); + done(); + }); + }); + + // delete bookmarked articles + it('should delete a bookmarked article', (done) => { + chai + .request(app) + .delete(`/api/v1/articles/${createdArticle.slug}/bookmark`) + .set('access-token', accessToken) + .end((err, res) => { + expect(res.status).to.be.equal(status.OK); + expect(res.body).to.not.include.keys('errors'); + done(); + }); + }); + + it('should not delete a bookmarked article if it has already been deleted', (done) => { + chai + .request(app) + .delete(`/api/v1/articles/${createdArticle.slug}/bookmark`) + .set('access-token', accessToken) + .end((err, res) => { + expect(res.status).to.be.equal(status.BAD_REQUEST); + expect(res.body).to.include.keys('errors'); + done(); + }); + }); + + it('should not delete a bookmarked article if the user is not authenticated', (done) => { + chai + .request(app) + .delete(`/api/v1/articles/${createdArticle.slug}/bookmark`) + .end((err, res) => { + expect(res.status).to.be.equal(status.UNAUTHORIZED); + expect(res.body).to.include.keys('errors'); + done(); + }); + }); + + it('should throw an error when deleting a bookmarked article', (done) => { + const invalidUserAccessToken = jwt.sign( + { id: {}, role: createdUser.role, permissions: createdUser.permissions }, + process.env.SECRET_KEY, + { expiresIn: '1d' } + ); + chai + .request(app) + .delete(`/api/v1/articles/${createdArticle.slug}/bookmark`) + .set('access-token', invalidUserAccessToken) + .end((err, res) => { + expect(res.status).to.be.equal(status.SERVER_ERROR); + expect(res.body).to.include.keys('errors'); + done(); + }); + }); + + // favorite an article + it('should favorite an article', (done) => { + chai + .request(app) + .patch(`/api/v1/articles/${createdArticle.slug}/favorite`) + .set('access-token', accessToken) + .end((err, res) => { + expect(res.status).to.be.equal(status.CREATED); + expect(res.body).to.not.include.keys('errors'); + done(); + }); + }); + + it('should not favorite an article if it has already been favorited by the same user', (done) => { + chai + .request(app) + .patch(`/api/v1/articles/${createdArticle.slug}/favorite`) + .set('access-token', accessToken) + .end((err, res) => { + expect(res.status).to.be.equal(status.EXIST); + expect(res.body).to.include.keys('errors'); + done(); + }); + }); + + it('should not favorite an article if the user is not authenticated', (done) => { + chai + .request(app) + .patch(`/api/v1/articles/${createdArticle.slug}/favorite`) + .end((err, res) => { + expect(res.status).to.be.equal(status.UNAUTHORIZED); + expect(res.body).to.include.keys('errors'); + done(); + }); + }); + + it("should not favorite an article if the user doesn't exist", (done) => { + const invalidUserAccessToken = jwt.sign( + { id: 0, role: createdUser.role, permissions: createdUser.permissions }, + process.env.SECRET_KEY, + { expiresIn: '1d' } + ); + chai + .request(app) + .patch(`/api/v1/articles/${createdArticle.slug}/favorite`) + .set('access-token', invalidUserAccessToken) + .end((err, res) => { + expect(res.status).to.be.equal(status.UNAUTHORIZED); + expect(res.body).to.include.keys('errors'); + done(); + }); + }); + + it('should throw an error when favoriteing an article', (done) => { + const invalidUserAccessToken = jwt.sign( + { id: {}, role: createdUser.role, permissions: createdUser.permissions }, + process.env.SECRET_KEY, + { expiresIn: '1d' } + ); + chai + .request(app) + .patch(`/api/v1/articles/${createdArticle.slug}/favorite`) + .set('access-token', invalidUserAccessToken) + .end((err, res) => { + expect(res.status).to.be.equal(status.SERVER_ERROR); + expect(res.body).to.include.keys('errors'); + done(); + }); + }); + + // get favorited articles + it('should get all favorited articles', (done) => { + chai + .request(app) + .get('/api/v1/articles/favorited') + .set('access-token', accessToken) + .end((err, res) => { + expect(res.status).to.be.equal(status.OK); + expect(res.body).to.not.include.keys('errors'); + done(); + }); + }); + + it('should not get all favorited articles if the user is not authenticated', (done) => { + chai + .request(app) + .get('/api/v1/articles/favorited') + .end((err, res) => { + expect(res.status).to.be.equal(status.UNAUTHORIZED); + expect(res.body).to.include.keys('errors'); + done(); + }); + }); + + it('should throw an error when getting favorited articles', (done) => { + const invalidUserAccessToken = jwt.sign( + { id: {}, role: createdUser.role, permissions: createdUser.permissions }, + process.env.SECRET_KEY, + { expiresIn: '1d' } + ); + chai + .request(app) + .get('/api/v1/articles/favorited') + .set('access-token', invalidUserAccessToken) + .end((err, res) => { + expect(res.status).to.be.equal(status.SERVER_ERROR); + expect(res.body).to.include.keys('errors'); + done(); + }); + }); + + // delete favorited articles + it('should delete a favorited article', (done) => { + chai + .request(app) + .delete(`/api/v1/articles/${createdArticle.slug}/favorite`) + .set('access-token', accessToken) + .end((err, res) => { + expect(res.status).to.be.equal(status.OK); + expect(res.body).to.not.include.keys('errors'); + done(); + }); + }); + + it('should not delete a favorited article if it has already been deleted', (done) => { + chai + .request(app) + .delete(`/api/v1/articles/${createdArticle.slug}/favorite`) + .set('access-token', accessToken) + .end((err, res) => { + expect(res.status).to.be.equal(status.BAD_REQUEST); + expect(res.body).to.include.keys('errors'); + done(); + }); + }); + + it('should not delete a favorited article if the user is not authenticated', (done) => { + chai + .request(app) + .delete(`/api/v1/articles/${createdArticle.slug}/favorite`) + .end((err, res) => { + expect(res.status).to.be.equal(status.UNAUTHORIZED); + expect(res.body).to.include.keys('errors'); + done(); + }); + }); + + it('should throw an error when deleting a favorited article', (done) => { + const invalidUserAccessToken = jwt.sign( + { id: {}, role: createdUser.role, permissions: createdUser.permissions }, + process.env.SECRET_KEY, + { expiresIn: '1d' } + ); + chai + .request(app) + .delete(`/api/v1/articles/${createdArticle.slug}/favorite`) + .set('access-token', invalidUserAccessToken) + .end((err, res) => { + expect(res.status).to.be.equal(status.SERVER_ERROR); + expect(res.body).to.include.keys('errors'); + done(); + }); + }); + + // share an article + it('should share an article on facebook', (done) => { + chai + .request(app) + .get(`/api/v1/articles/${createdArticle.slug}/share/facebook`) + .set('access-token', accessToken) + .end((err, res) => { + expect(res.status).to.be.equal(status.OK); + done(); + }); + }); + + it('should share an article on twitter', (done) => { + chai + .request(app) + .get(`/api/v1/articles/${createdArticle.slug}/share/twitter`) + .set('access-token', accessToken) + .end((err, res) => { + expect(res.status).to.be.equal(status.OK); + done(); + }); + }); + + it('should share an article on linkedin', (done) => { + chai + .request(app) + .get(`/api/v1/articles/${createdArticle.slug}/share/linkedin`) + .set('access-token', accessToken) + .end((err, res) => { + expect(res.status).to.be.equal(status.OK); + done(); + }); + }); + + it('should share an article on gmail', (done) => { + chai + .request(app) + .get(`/api/v1/articles/${createdArticle.slug}/share/gmail`) + .set('access-token', accessToken) + .end((err, res) => { + expect(res.status).to.be.equal(status.OK); + done(); + }); + }); +}); diff --git a/src/tests/routes/auth.test.js b/src/tests/routes/auth.test.js new file mode 100644 index 00000000..04024554 --- /dev/null +++ b/src/tests/routes/auth.test.js @@ -0,0 +1,120 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import chai from 'chai'; +import chaiHttp from 'chai-http'; +import jwt from 'jsonwebtoken'; +import dotenv from 'dotenv'; +import * as Factory from '../../helpers/factory'; +import db from '../../models'; +import app from '../../app'; + +dotenv.config(); + +chai.use(chaiHttp); +chai.should(); + +const newUser = Factory.user.build(); +delete newUser.id; + +const token = jwt.sign({ email: 'luctunechi45@gmail.com' }, process.env.SECRET_KEY, { + expiresIn: '1d' +}); + +describe('Authentication', () => { + before(async () => { + try { + await db.User.destroy({ + truncate: true, + cascade: true, + logging: false + }); + await db.User.create(newUser, { logging: false }); + } catch (error) { + throw error; + } + }); + + describe('Reset Password', () => { + it('should send the user a password reset link via email', (done) => { + chai + .request(app) + .post('/api/v1/auth/reset/') + .send(newUser) + .end((err, res) => { + res.should.have.status(200); + res.should.be.a('object'); + done(); + }); + }); + + it('it should not send an email when email not found', (done) => { + chai + .request(app) + .post('/api/v1/auth/reset/') + .send({ email: 'luctunechissas45@gmail.com' }) + .end((err, res) => { + res.should.have.status(404); + res.body.should.be.a('object'); + done(); + }); + }); + + it('it should update password with valid token', (done) => { + chai + .request(app) + .patch(`/api/v1/auth/reset/${token}`) + .send({ + passwordOne: 'Brazzaville10!', + passwordTwo: 'Brazzaville10!' + }) + .end((err, res) => { + chai.expect(res.body).to.be.an('object'); + done(); + }); + }); + + it('it should not update password when password does not match', (done) => { + chai + .request(app) + .patch(`/api/v1/auth/reset/${token}`) + .send({ + passwordOne: 'Brazzaville10!', + passwordTwo: 'Brazzaville10!!' + }) + .end((err, res) => { + chai.expect(res.status).to.eql(400); + chai.expect(res.body).to.be.an('object'); + done(); + }); + }); + + it('it should not update password when password are empty', (done) => { + chai + .request(app) + .patch(`/api/v1/auth/reset/${token}`) + .send({ + passwordOne: '', + passwordTwo: '' + }) + .end((err, res) => { + chai.expect(res.status).to.eql(400); + chai.expect(res.body).to.be.an('object'); + done(); + }); + }); + + it('it should not update password when password are not valid', (done) => { + chai + .request(app) + .patch(`/api/v1/auth/reset/${token}`) + .send({ + passwordOne: 'Brazzaville10', + passwordTwo: 'Brazzaville10' + }) + .end((err, res) => { + chai.expect(res.status).to.eql(400); + chai.expect(res.body).to.be.an('object'); + done(); + }); + }); + }); +}); diff --git a/src/tests/routes/chats.test.js b/src/tests/routes/chats.test.js new file mode 100644 index 00000000..315c6a02 --- /dev/null +++ b/src/tests/routes/chats.test.js @@ -0,0 +1,168 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import chai from 'chai'; +import jwt from 'jsonwebtoken'; +import dotenv from 'dotenv'; +import db from '../../models'; +import status from '../../config/status'; +import * as Factory from '../../helpers/factory'; +import app from '../../app'; + +dotenv.config(); + +const { expect } = chai; + +let accessToken = ''; +let createdUser = ''; +let savedChat = ''; + +const user = Factory.user.build(); +const article = Factory.article.build(); + +delete user.id; +delete article.id; + +describe('Chats', () => { + before(async () => { + try { + await db.User.destroy({ + truncate: true, + cascade: true, + logging: false + }); + createdUser = (await db.User.create(user, { logging: false })).dataValues; + accessToken = jwt.sign( + { id: createdUser.id, role: createdUser.role, permissions: createdUser.permissions }, + process.env.SECRET_KEY, + { expiresIn: '1d' } + ); + } catch (error) { + throw error; + } + }); + + // save a chat message + it('should save a chat message', (done) => { + chai + .request(app) + .post('/api/v1/chats') + .set('access-token', accessToken) + .send({ message: 'hello' }) + .end((err, res) => { + savedChat = res.body.chat; + expect(res.status).to.be.equal(status.OK); + expect(res.body).to.not.include.keys('errors'); + done(); + }); + }); + + it('should not save a chat message if it the body is empty or the message is not a string', (done) => { + chai + .request(app) + .post('/api/v1/chats') + .set('access-token', accessToken) + .send({}) + .end((err, res) => { + expect(res.status).to.be.equal(status.BAD_REQUEST); + expect(res.body).to.include.keys('errors'); + done(); + }); + }); + + it("should not save a chat message if the user doesn't exist", (done) => { + const invalidUserAccessToken = jwt.sign( + { id: 0, role: createdUser.role, permissions: createdUser.permissions }, + process.env.SECRET_KEY, + { expiresIn: '1d' } + ); + chai + .request(app) + .post('/api/v1/chats') + .set('access-token', invalidUserAccessToken) + .send({ message: 'hello' }) + .end((err, res) => { + expect(res.status).to.be.equal(status.UNAUTHORIZED); + expect(res.body).to.include.keys('errors'); + done(); + }); + }); + + it('should throw an error', (done) => { + const invalidUserAccessToken = jwt.sign( + { id: {}, role: createdUser.role, permissions: createdUser.permissions }, + process.env.SECRET_KEY, + { expiresIn: '1d' } + ); + chai + .request(app) + .post('/api/v1/chats') + .set('access-token', invalidUserAccessToken) + .send({ message: 'hello' }) + .end((err, res) => { + expect(res.status).to.be.equal(status.SERVER_ERROR); + expect(res.body).to.include.keys('errors'); + done(); + }); + }); + + // get chats + it('should get all chats', (done) => { + chai + .request(app) + .get('/api/v1/chats') + .set('access-token', accessToken) + .end((err, res) => { + expect(res.status).to.be.equal(status.OK); + expect(res.body).to.not.include.keys('errors'); + done(); + }); + }); + + it('should get all chats', (done) => { + chai + .request(app) + .get('/api/v1/chats?limit=1&offset=0') + .set('access-token', accessToken) + .end((err, res) => { + expect(res.status).to.be.equal(status.OK); + expect(res.body).to.not.include.keys('errors'); + done(); + }); + }); + + // remove a chat + it('should remove a chat', (done) => { + chai + .request(app) + .delete(`/api/v1/chats/${savedChat.id}`) + .set('access-token', accessToken) + .end((err, res) => { + expect(res.status).to.be.equal(status.OK); + expect(res.body).to.not.include.keys('errors'); + done(); + }); + }); + + it('should not remove a chat', (done) => { + chai + .request(app) + .delete(`/api/v1/chats/${savedChat.id}`) + .set('access-token', accessToken) + .end((err, res) => { + expect(res.status).to.be.equal(status.NOT_FOUND); + expect(res.body).to.include.keys('errors'); + done(); + }); + }); + + it('should throw an error if the parameter passed is not valid', (done) => { + chai + .request(app) + .delete('/api/v1/chats/{}') + .set('access-token', accessToken) + .end((err, res) => { + expect(res.status).to.be.equal(status.BAD_REQUEST); + expect(res.body).to.include.keys('errors'); + done(); + }); + }); +}); diff --git a/src/tests/routes/commentLikes.test.js b/src/tests/routes/commentLikes.test.js new file mode 100644 index 00000000..f0d20167 --- /dev/null +++ b/src/tests/routes/commentLikes.test.js @@ -0,0 +1,120 @@ +/* eslint-disable no-undef */ +/* eslint-disable import/no-extraneous-dependencies */ +import chai from 'chai'; +import chaiHttp from 'chai-http'; +import jwt from 'jsonwebtoken'; +import * as Factory from '../../helpers/factory'; +import status from '../../config/status'; +import db from '../../models'; +import app from '../../app'; + +chai.use(chaiHttp); +chai.should(); +let accessToken; +let createdUser = {}; +let createdArticle = {}; +let createdComment = {}; +let commentId; +let newarticleSlug; + +const newUser = Factory.user.build(); +const newArticle = Factory.article.build(); +const newComment = Factory.comment.build(); + +delete newUser.id; +delete newArticle.id; +delete newComment.id; + +describe('Comment likes', () => { + before(async () => { + try { + await db.User.destroy({ + truncate: true, + cascade: true, + logging: false + }); + await db.Article.destroy({ + truncate: true, + cascade: true, + logging: false + }); + await db.Comment.destroy({ + truncate: true, + cascade: true, + logging: false + }); + + createdUser = (await db.User.create(newUser, { logging: false })).dataValues; + newArticle.userId = createdUser.id; + accessToken = jwt.sign( + { id: createdUser.id, role: createdUser.role, permissions: createdUser.permissions }, + process.env.SECRET_KEY, + { expiresIn: '1d' } + ); + createdArticle = (await db.Article.create(newArticle, { logging: false })).dataValues; + + newComment.articleSlug = createdArticle.slug; + createdComment = await db.Comment.create(newComment, { logging: false }); + newarticleSlug = createdComment.articleSlug; + commentId = createdComment.id; + } catch (err) { + throw err; + } + }); + it('Should let the user create a comment like', (done) => { + chai + .request(app) + .post(`/api/v1/articles/${newarticleSlug}/comments/${createdComment.id}/like`) + .set('access-token', accessToken) + .send() + .end((err, res) => { + res.should.have.status(status.CREATED); + done(); + }); + }); + it('Should not let the user create a comment like with and article slug that does not exist', (done) => { + chai + .request(app) + .post(`/api/v1/articles/${createdArticle.slg}/comments/${createdComment.id}/like`) + .set('access-token', accessToken) + .send() + .end((err, res) => { + res.should.have.status(status.NOT_FOUND); + done(); + }); + }); + it('Should not let the user create a comment like with and commentId that does not exist', (done) => { + chai + .request(app) + .post(`/api/v1/articles/${createdArticle.slg}/comments/${createdComment.id}e/like`) + .set('access-token', accessToken) + .send() + .end((err, res) => { + res.should.have.status(status.NOT_FOUND); + done(); + }); + }); + it('Should let a user see who liked the comment ', (done) => { + chai + .request(app) + .get(`/api/v1/articles/${newarticleSlug}/comments/${createdComment.id}/likes`) + .set('access-token', accessToken) + .send() + .end((err, res) => { + res.should.have.status(status.OK); + done(); + }); + }); + + it('Should not let the user create a comment like again', (done) => { + chai + .request(app) + .post(`/api/v1/articles/${newarticleSlug}/comments/${commentId}/like`) + .set('access-token', accessToken) + .send() + .end((err, res) => { + res.should.have.status(status.OK); + done(); + }); + }); +}); diff --git a/src/tests/routes/comments.test.js b/src/tests/routes/comments.test.js new file mode 100644 index 00000000..00ed3371 --- /dev/null +++ b/src/tests/routes/comments.test.js @@ -0,0 +1,169 @@ +/* eslint-disable no-undef */ +/* eslint-disable import/no-extraneous-dependencies */ +import chai from 'chai'; +import chaiHttp from 'chai-http'; +import jwt from 'jsonwebtoken'; +import * as Factory from '../../helpers/factory'; +import status from '../../config/status'; +import db from '../../models'; +import app from '../../app'; + +chai.use(chaiHttp); +chai.should(); +let accessToken; +let createdUser = {}; +let createdArticle = {}; +let createdComment = {}; +let commentId; +let newarticleSlug; + +const newUser = Factory.user.build(); +const newArticle = Factory.article.build(); +const newComment = Factory.comment.build(); + +delete newUser.id; +delete newArticle.id; +delete newComment.id; + +describe('COMMENTS', () => { + before(async () => { + try { + await db.User.destroy({ + truncate: true, + cascade: true, + logging: false + }); + await db.Article.destroy({ + truncate: true, + cascade: true, + logging: false + }); + await db.Comment.destroy({ + truncate: true, + cascade: true, + logging: false + }); + + createdUser = (await db.User.create(newUser, { logging: false })).dataValues; + newArticle.userId = createdUser.id; + accessToken = jwt.sign( + { id: createdUser.id, role: createdUser.role, permissions: createdUser.permissions }, + process.env.SECRET_KEY, + { expiresIn: '1d' } + ); + createdArticle = (await db.Article.create(newArticle, { logging: false })).dataValues; + newComment.articleSlug = createdArticle.slug; + createdComment = await db.Comment.create(newComment, { logging: false }); + newarticleSlug = createdComment.articleSlug; + commentId = createdComment.id; + } catch (err) { + throw err; + } + }); + + it('Should let the user create a comment', (done) => { + chai + .request(app) + .post(`/api/v1/articles/${createdComment.articleSlug}/comments`) + .set('access-token', accessToken) + .send({ + body: 'They called me here' + }) + .end((err, res) => { + res.should.have.status(status.CREATED); + done(); + }); + }); + + it('Should not let the user create a comment with the invalid inputs in body', (done) => { + newComment.userId = createdUser.id; + chai + .request(app) + .post(`/api/v1/articles/${createdArticle.slug}/comments`) + .set('access-token', accessToken) + .send('~~~') + .end((err, res) => { + res.should.have.status(status.BAD_REQUEST); + done(); + }); + }); + + it('Should not let the user create a comment with a article slug which does not exist', (done) => { + chai + .request(app) + .post('/api/v1/articles/dhbdjhfbjd/comments') + .set('access-token', accessToken) + .send(newComment) + .end((err, res) => { + createdComment = res.body; + res.should.have.status(status.BAD_REQUEST); + done(); + }); + }); + + it('should get all comments of a specific article', (done) => { + chai + .request(app) + .get(`/api/v1/articles/${createdArticle.slug}/comments`) + .set('access-token', accessToken) + .end((error, res) => { + res.should.have.status(status.OK); + done(); + }); + }); + + it('Should not let the user get all comments with the wrong article slug', (done) => { + chai + .request(app) + .get('/api/v1/articles/djbdfkjsdsfsd/comments') + .set('access-token', accessToken) + .end((error, res) => { + res.should.have.status(status.NOT_FOUND); + done(); + }); + }); + + it('Should not let the user get all comments with the wrong article slug', (done) => { + chai + .request(app) + .get('/jjjjj') + .set('access-token', accessToken) + .end((error, res) => { + res.should.have.status(status.NOT_FOUND); + done(); + }); + }); + + it('Should not let the user delete a comment with a wrong article slug', (done) => { + chai + .request(app) + .delete('/api/v1/articles/kjscofljos;fj/comments/1') + .set('access-token', accessToken) + .send(newComment) + .end((err, res) => { + res.should.have.status(status.NOT_FOUND); + done(); + }); + }); + it('Should not let the user delete a comment with a comment Id that does not exist', (done) => { + chai + .request(app) + .delete(`/api/v1/articles/${newarticleSlug}/comments/57647634`) + .set('access-token', accessToken) + .send(newComment) + .end((err, res) => { + res.should.have.status(status.NOT_FOUND); + done(); + }); + }); + it('Should let the user delete a comment', (done) => { + chai + .request(app) + .delete(`/api/v1/articles/${newarticleSlug}/comments/${commentId}`) + .set('access-token', accessToken) + .end((err, res) => { + res.should.have.status(200); + done(); + }); + }); +}); diff --git a/src/tests/routes/editComment.test.js b/src/tests/routes/editComment.test.js new file mode 100644 index 00000000..6e4fa88a --- /dev/null +++ b/src/tests/routes/editComment.test.js @@ -0,0 +1,162 @@ +/* eslint-disable no-undef */ +/* eslint-disable import/no-extraneous-dependencies */ +import chai from 'chai'; +import chaiHttp from 'chai-http'; +import jwt from 'jsonwebtoken'; +import * as Factory from '../../helpers/factory'; +import status from '../../config/status'; +import db from '../../models'; +import app from '../../app'; + +chai.use(chaiHttp); +chai.should(); +let accessToken; +let createdUser = {}; +let createdArticle = {}; +let createdComment = {}; +let commentId; +let newarticleSlug; +let createdEdit; +let editId; + +const newUser = Factory.user.build(); +const newArticle = Factory.article.build(); +const newComment = Factory.comment.build(); +const newEdit = Factory.editComment.build(); + +delete newUser.id; +delete newArticle.id; +delete newComment.id; +delete newEdit.id; + +describe('COMMENT EDITS', () => { + before(async () => { + try { + await db.User.destroy({ + truncate: true, + cascade: true, + logging: false + }); + await db.Article.destroy({ + truncate: true, + cascade: true, + logging: false + }); + await db.Comment.destroy({ + truncate: true, + cascade: true, + logging: false + }); + await db.CommentEdit.destroy({ + truncate: true, + cascade: true, + logging: false + }); + + createdUser = (await db.User.create(newUser, { logging: false })).dataValues; + newArticle.userId = createdUser.id; + accessToken = jwt.sign( + { id: createdUser.id, role: createdUser.role, permissions: createdUser.permissions }, + process.env.SECRET_KEY, + { expiresIn: '1d' } + ); + createdArticle = (await db.Article.create(newArticle, { logging: false })).dataValues; + newComment.articleSlug = createdArticle.slug; + createdComment = await db.Comment.create(newComment, { logging: false }); + newEdit.articleSlug = createdArticle.slug; + newEdit.userId = createdUser.id; + newEdit.commentId = createdComment.id; + createdEdit = await db.CommentEdit.create(newEdit, { logging: false }); + editId = createdEdit.id; + newarticleSlug = createdArticle.slug; + commentId = createdComment.id; + } catch (err) { + throw err; + } + }); + it('Should let the user create a comment', (done) => { + const comment = { + body: 'the edit comment' + }; + chai + .request(app) + .post(`/api/v1/articles/${newarticleSlug}/comments`) + .set('access-token', accessToken) + .send(comment) + .end((err, res) => { + newarticleSlug = res.body.comment.articleSlug; + commentId = res.body.comment.id; + res.should.have.status(status.CREATED); + done(); + }); + }); + it('Should let the user get only the comment when it is not edited', (done) => { + chai + .request(app) + .get(`/api/v1/articles/${newarticleSlug}/comments/${commentId}/edits`) + .set('access-token', accessToken) + .send() + .end((err, res) => { + res.should.have.status(status.OK); + done(); + }); + }); + it('Should let the user edit a comment', (done) => { + const comment = { + body: 'the edit comment' + }; + chai + .request(app) + .patch(`/api/v1/articles/${newarticleSlug}/comments/${commentId}`) + .set('access-token', accessToken) + .send(comment) + .end((err, res) => { + res.should.have.status(status.OK); + done(); + }); + }); + it('Should let the user get the list of all edited comment', (done) => { + chai + .request(app) + .get(`/api/v1/articles/${newarticleSlug}/comments/${commentId}/edits`) + .set('access-token', accessToken) + .send() + .end((err, res) => { + res.should.have.status(status.OK); + done(); + }); + }); + it('Should let the user get the list of all edited comment', (done) => { + chai + .request(app) + .get(`/api/v1/articles/${newarticleSlug}/comments/${commentId}/edits`) + .set('access-token', accessToken) + .send() + .end((err, res) => { + res.should.have.status(status.OK); + done(); + }); + }); + it('Should not let the user delete a comment that does not exist', (done) => { + chai + .request(app) + .delete(`/api/v1/articles/${newarticleSlug}/comments/${commentId}/edits/0000`) + .set('access-token', accessToken) + .send() + .end((err, res) => { + res.should.have.status(status.NOT_FOUND); + done(); + }); + }); + it('Should let the user delete a comment from edit history', (done) => { + chai + .request(app) + .delete(`/api/v1/articles/${newarticleSlug}/comments/${commentId}/edits/${editId}`) + .set('access-token', accessToken) + .send() + .end((err, res) => { + res.should.have.status(status.OK); + done(); + }); + }); +}); diff --git a/src/tests/routes/highlight.test.js b/src/tests/routes/highlight.test.js new file mode 100644 index 00000000..34b3c428 --- /dev/null +++ b/src/tests/routes/highlight.test.js @@ -0,0 +1,125 @@ +/* eslint-disable no-undef */ +/* eslint-disable import/no-extraneous-dependencies */ +import chai from 'chai'; +import chaiHttp from 'chai-http'; +import jwt from 'jsonwebtoken'; +import * as Factory from '../../helpers/factory'; +import status from '../../config/status'; +import db from '../../models'; +import app from '../../app'; + +chai.use(chaiHttp); +chai.should(); +let accessToken; +let createdUser = {}; +let createdArticle = {}; +let createdHighlight = {}; +let highlightId; +let newArticleSlug; + +const newUser = Factory.user.build(); +const newArticle = Factory.article.build(); +const newHighlight = Factory.highlight.build(); + +delete newUser.id; +delete newArticle.id; +delete newHighlight.id; + +describe('Highlight on Article', () => { + before(async () => { + try { + await db.User.destroy({ + truncate: true, + cascade: true, + logging: false + }); + + createdUser = (await db.User.create(newUser, { logging: false })).dataValues; + newArticle.userId = createdUser.id; + accessToken = jwt.sign( + { id: createdUser.id, role: createdUser.role, permissions: createdUser.permissions }, + process.env.SECRET_KEY, + { expiresIn: '1d' } + ); + createdArticle = (await db.Article.create(newArticle, { logging: false })).dataValues; + newHighlight.userId = createdArticle.userId; + newHighlight.articleSlug = createdArticle.slug; + createdHighlight = await db.Highlight.create(newHighlight, { logging: false }); + newArticleSlug = createdHighlight.articleSlug; + highlightId = createdHighlight.id; + } catch (err) { + throw err; + } + }); + + it('Should let the user create a highlight', (done) => { + chai + .request(app) + .post(`/api/v1/${newArticleSlug}/highlights`) + .set('access-token', accessToken) + .send({ + anchorKey: 'anchorKey', + highlightedText: 'on sera ensemble bientotssssss', + startIndex: 0, + stopIndex: 30, + comment: 'welcomme to the party' + }) + .end((err, res) => { + res.should.have.status(status.CREATED); + done(); + }); + }); + + it('Should not let the user create highlight with the invalid inputs in body', (done) => { + newHighlight.userId = createdUser.id; + chai + .request(app) + .post(`/api/v1/${newArticleSlug}/highlights`) + .set('access-token', accessToken) + .send('_____') + .end((err, res) => { + res.should.have.status(status.BAD_REQUEST); + done(); + }); + }); + + it('Should return bad request when the highlighted text does not match with start and end index', (done) => { + chai + .request(app) + .post(`/api/v1/${newArticleSlug}/highlights`) + .set('access-token', accessToken) + .send({ + anchorKey: 'anchorKey', + highlightedText: 'on sera ensemble bientotssssss', + startIndex: 0, + stopIndex: 39, + comment: 'welcomme to the party' + }) + .end((err, res) => { + res.should.have.status(status.BAD_REQUEST); + done(); + }); + }); + + it('should get all highlight of a specific article', (done) => { + chai + .request(app) + .get(`/api/v1/${newArticleSlug}/highlights`) + .set('access-token', accessToken) + .end((error, res) => { + res.should.have.status(status.OK); + done(); + }); + }); + + it('Should let the user delete a highlight', (done) => { + chai + .request(app) + .delete(`/api/v1/${newArticleSlug}/highlights/${highlightId}`) + .set('access-token', accessToken) + .end((err, res) => { + res.should.have.status(status.OK); + done(); + }); + }); +}); diff --git a/src/tests/routes/index.js b/src/tests/routes/index.js new file mode 100644 index 00000000..22c4e801 --- /dev/null +++ b/src/tests/routes/index.js @@ -0,0 +1,18 @@ +import './readingStats.test'; +import './comments.test'; +import './404.test'; +import './auth.test'; +import './articles.test'; +import './users.test'; +import './chats.test'; +import './userArticles.test'; +import './commentLikes.test'; +import './articleLike.test'; +import './report.test'; +import './editComment.test'; +import './allUser.test'; +import './roles.test'; +import './permission.test'; +import './highlight.test'; +import './notifications.test'; +import './readingStats.test'; diff --git a/src/tests/routes/notifications.test.js b/src/tests/routes/notifications.test.js new file mode 100644 index 00000000..5772c13d --- /dev/null +++ b/src/tests/routes/notifications.test.js @@ -0,0 +1,336 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import chai from 'chai'; +import jwt from 'jsonwebtoken'; +import dotenv from 'dotenv'; +import db from '../../models'; +import status from '../../config/status'; +import * as Factory from '../../helpers/factory'; +import app from '../../app'; + +dotenv.config(); + +const { expect } = chai; + +let accessToken = ''; +let fakeToken = ''; +let createdUser = ''; +let createdNotification = ''; + +const user = Factory.user.build(); +const notificationConfig = Factory.notificationConfig.build(); + +delete user.id; + +describe('Notifications', () => { + before(async () => { + await db.User.destroy({ + truncate: true, + cascade: true, + logging: false + }); + createdUser = (await db.User.create(user, { logging: false })).dataValues; + createdNotification = (await db.Notification.create( + Factory.notification.build({ userId: createdUser.id }), + { logging: false } + )).dataValues; + accessToken = jwt.sign( + { id: createdUser.id, role: createdUser.role, permissions: createdUser.permissions }, + process.env.SECRET_KEY, + { expiresIn: '1d' } + ); + + fakeToken = jwt.sign( + { id: {}, role: createdUser.role, permissions: createdUser.permissions }, + process.env.SECRET_KEY, + { expiresIn: '1d' } + ); + }); + + // set notification config + it('should set notification config', (done) => { + chai + .request(app) + .post('/api/v1/notifications/configuration') + .set('access-token', accessToken) + .send(notificationConfig) + .end((err, res) => { + expect(res.status).to.be.equal(status.CREATED); + expect(res.body).to.not.include.keys('errors'); + done(); + }); + }); + + it('should not set notification config if the user has already set configurations', (done) => { + chai + .request(app) + .post('/api/v1/notifications/configuration') + .set('access-token', accessToken) + .send(notificationConfig) + .end((err, res) => { + expect(res.status).to.be.equal(status.EXIST); + expect(res.body).to.include.keys('errors'); + done(); + }); + }); + + it('should not set notification config if the configurations are malformed', (done) => { + chai + .request(app) + .post('/api/v1/notifications/configuration') + .set('access-token', accessToken) + .send({}) + .end((err, res) => { + expect(res.status).to.be.equal(status.BAD_REQUEST); + expect(res.body).to.include.keys('errors'); + done(); + }); + }); + + // get notification config + it('should get notification config', (done) => { + chai + .request(app) + .get('/api/v1/notifications/configuration') + .set('access-token', accessToken) + .end((err, res) => { + expect(res.status).to.be.equal(status.OK); + expect(res.body).to.not.include.keys('errors'); + done(); + }); + }); + + it('should not get notification config', (done) => { + chai + .request(app) + .get('/api/v1/notifications/configuration') + .set('access-token', fakeToken) + .end((err, res) => { + expect(res.status).to.be.equal(status.SERVER_ERROR); + expect(res.body).to.include.keys('errors'); + done(); + }); + }); + + // update notification config + it('should update notification config', (done) => { + chai + .request(app) + .put('/api/v1/notifications/configuration') + .set('access-token', accessToken) + .send(notificationConfig) + .end((err, res) => { + expect(res.status).to.be.equal(status.OK); + expect(res.body).to.not.include.keys('errors'); + done(); + }); + }); + + it('should not update notification config if the configurations are malformed', (done) => { + chai + .request(app) + .put('/api/v1/notifications/configuration') + .set('access-token', accessToken) + .send({}) + .end((err, res) => { + expect(res.status).to.be.equal(status.BAD_REQUEST); + expect(res.body).to.include.keys('errors'); + done(); + }); + }); + + it('should not update notification config', (done) => { + chai + .request(app) + .put('/api/v1/notifications/configuration') + .set('access-token', fakeToken) + .send(notificationConfig) + .end((err, res) => { + expect(res.status).to.be.equal(status.SERVER_ERROR); + expect(res.body).to.include.keys('errors'); + done(); + }); + }); + + // get notifications + it('should get all notifications', (done) => { + chai + .request(app) + .get('/api/v1/notifications') + .set('access-token', accessToken) + .end((err, res) => { + expect(res.status).to.be.equal(status.OK); + expect(res.body).to.not.include.keys('errors'); + done(); + }); + }); + + it('should get all unseen notifications', (done) => { + chai + .request(app) + .get('/api/v1/notifications/unseen') + .set('access-token', accessToken) + .end((err, res) => { + expect(res.status).to.be.equal(status.OK); + expect(res.body).to.not.include.keys('errors'); + done(); + }); + }); + + it('should get all seen notifications', (done) => { + chai + .request(app) + .get('/api/v1/notifications/seen') + .set('access-token', accessToken) + .end((err, res) => { + expect(res.status).to.be.equal(status.OK); + expect(res.body).to.not.include.keys('errors'); + done(); + }); + }); + + it('should not get notifications if the user ID is invalid', (done) => { + chai + .request(app) + .get('/api/v1/notifications') + .set('access-token', fakeToken) + .end((err, res) => { + expect(res.status).to.be.equal(status.SERVER_ERROR); + expect(res.body).to.include.keys('errors'); + done(); + }); + }); + + it('should get one notification by ID', (done) => { + chai + .request(app) + .get(`/api/v1/notifications/${createdNotification.id}`) + .set('access-token', accessToken) + .end((err, res) => { + expect(res.status).to.be.equal(status.OK); + expect(res.body).to.not.include.keys('errors'); + done(); + }); + }); + + it('should not get one a notification if the provided ID is not valid', (done) => { + chai + .request(app) + .get('/api/v1/notifications/{}') + .set('access-token', accessToken) + .end((err, res) => { + expect(res.status).to.be.equal(status.BAD_REQUEST); + expect(res.body).to.include.keys('errors'); + done(); + }); + }); + + // update notifications + it('should update the status notifications to "seen"', (done) => { + chai + .request(app) + .put('/api/v1/notifications/seen') + .set('access-token', accessToken) + .end((err, res) => { + expect(res.status).to.be.equal(status.OK); + expect(res.body).to.not.include.keys('errors'); + done(); + }); + }); + + it('should update the status notifications to "unseen"', (done) => { + chai + .request(app) + .put('/api/v1/notifications/unseen') + .set('access-token', accessToken) + .end((err, res) => { + expect(res.status).to.be.equal(status.OK); + expect(res.body).to.not.include.keys('errors'); + done(); + }); + }); + + it('should update the status of a notification to "seen"', (done) => { + chai + .request(app) + .put(`/api/v1/notifications/${createdNotification.id}/seen`) + .set('access-token', accessToken) + .end((err, res) => { + expect(res.status).to.be.equal(status.OK); + expect(res.body).to.not.include.keys('errors'); + done(); + }); + }); + + it('should update the status of a notification to "unseen"', (done) => { + chai + .request(app) + .put(`/api/v1/notifications/${createdNotification.id}/unseen`) + .set('access-token', accessToken) + .end((err, res) => { + expect(res.status).to.be.equal(status.OK); + expect(res.body).to.not.include.keys('errors'); + done(); + }); + }); + + it('should not update the status of a notification if the ID is not valid', (done) => { + chai + .request(app) + .put('/api/v1/notifications/{}/seen') + .set('access-token', accessToken) + .end((err, res) => { + expect(res.status).to.be.equal(status.BAD_REQUEST); + expect(res.body).to.include.keys('errors'); + done(); + }); + }); + + it('should not update the status of a notification if the ID is not valid', (done) => { + chai + .request(app) + .put('/api/v1/notifications/{}/unseen') + .set('access-token', accessToken) + .end((err, res) => { + expect(res.status).to.be.equal(status.BAD_REQUEST); + expect(res.body).to.include.keys('errors'); + done(); + }); + }); + + // remove a notification + it('should remove a notification', (done) => { + chai + .request(app) + .delete(`/api/v1/notifications/${createdNotification.id}`) + .set('access-token', accessToken) + .end((err, res) => { + expect(res.status).to.be.equal(status.OK); + expect(res.body).to.not.include.keys('errors'); + done(); + }); + }); + + it('should not remove a notification', (done) => { + chai + .request(app) + .delete(`/api/v1/notifications/${createdNotification.id}`) + .set('access-token', accessToken) + .end((err, res) => { + expect(res.status).to.be.equal(status.NOT_FOUND); + expect(res.body).to.include.keys('errors'); + done(); + }); + }); + + it('should throw an error if the parameter passed is not valid', (done) => { + chai + .request(app) + .delete('/api/v1/notifications/{}') + .set('access-token', accessToken) + .end((err, res) => { + expect(res.status).to.be.equal(status.BAD_REQUEST); + expect(res.body).to.include.keys('errors'); + done(); + }); + }); +}); diff --git a/src/tests/routes/permission.test.js b/src/tests/routes/permission.test.js new file mode 100644 index 00000000..5d9db311 --- /dev/null +++ b/src/tests/routes/permission.test.js @@ -0,0 +1,95 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import chai from 'chai'; +import jwt from 'jsonwebtoken'; +import chaiHttp from 'chai-http'; +import app from '../../app'; +import status from '../../config/status'; +import db from '../../models'; +import * as Factory from '../../helpers/factory'; + +let createdUser = ''; +let token = ''; + +chai.should(); +chai.use(chaiHttp); +const { userType, permissions } = Factory.permissionsNormal.build(); + +const user = Factory.user.build(); +delete user.id; + +describe('Permission tests', () => { + // test signup; + before(async () => { + try { + await db.User.destroy({ + truncate: true, + cascade: true, + logging: false + }); + await db.Permission.destroy({ + truncate: true, + cascade: true, + logging: false + }); + createdUser = (await db.User.create(user, { logging: false })).dataValues; + token = jwt.sign( + { id: createdUser.id, role: createdUser.role, permissions: user.permissions }, + process.env.SECRET_KEY, + { + expiresIn: '1d' + } + ); + } catch (error) { + throw error; + } + }); + describe('Create permission', () => { + it('Should create new Permission', (done) => { + chai + .request(app) + .post('/api/v1/permissions') + .send({ userType, permissions }) + .set('access-token', token) + .end((err, res) => { + res.status.should.equal(status.CREATED); + res.body.should.be.an('object'); + done(); + }); + }); + + it('Should return exist when permission is already created', (done) => { + chai + .request(app) + .post('/api/v1/permissions') + .send({ userType, permissions }) + .set('access-token', token) + .end((err, res) => { + res.status.should.equal(status.EXIST); + res.body.should.be.an('object'); + done(); + }); + }); + + it('Should return all permissions', (done) => { + chai + .request(app) + .get('/api/v1/permissions') + .set('access-token', token) + .end((err, res) => { + res.status.should.equal(status.OK); + done(); + }); + }); + + it('Should return all permissions', (done) => { + chai + .request(app) + .get('/api/v1/permissions/normal') + .set('access-token', token) + .end((err, res) => { + res.status.should.equal(status.OK); + done(); + }); + }); + }); +}); diff --git a/src/tests/routes/readingStats.test.js b/src/tests/routes/readingStats.test.js new file mode 100644 index 00000000..ee5db1a8 --- /dev/null +++ b/src/tests/routes/readingStats.test.js @@ -0,0 +1,66 @@ +/* eslint-disable no-undef */ +/* eslint-disable import/no-extraneous-dependencies */ +import chai from 'chai'; +import chaiHttp from 'chai-http'; +import jwt from 'jsonwebtoken'; +import * as Factory from '../../helpers/factory'; +import status from '../../config/status'; +import db from '../../models'; +import app from '../../app'; + +chai.use(chaiHttp); +chai.should(); +let accessToken; +let createdUser = {}; +let createdArticle = {}; + +const newUser = Factory.user.build(); +const newArticle = Factory.article.build(); + +delete newUser.id; +delete newArticle.id; + +describe('Reading Stats', () => { + before(async () => { + try { + await db.User.destroy({ + truncate: true, + cascade: true, + logging: false + }); + + createdUser = (await db.User.create(newUser, { logging: false })).dataValues; + accessToken = jwt.sign( + { id: createdUser.id, role: createdUser.role, permissions: createdUser.permissions }, + process.env.SECRET_KEY, + { expiresIn: '1d' } + ); + newArticle.userId = createdUser.id; + createdArticle = (await db.Article.create(newArticle, { logging: false })).dataValues; + } catch (err) { + throw err; + } + }); + + it('Should let the user save a reading stats', (done) => { + chai + .request(app) + .post(`/api/v1/user/profile/${createdArticle.slug}/stats`) + .set('access-token', accessToken) + .end((err, res) => { + res.should.have.status(status.OK); + done(); + }); + }); + + it('Should let the user get reading stats', (done) => { + chai + .request(app) + .get('/api/v1/user/profile/stats') + .set('access-token', accessToken) + .end((err, res) => { + res.should.have.status(status.OK); + done(); + }); + }); +}); diff --git a/src/tests/routes/report.test.js b/src/tests/routes/report.test.js new file mode 100644 index 00000000..c063ae3f --- /dev/null +++ b/src/tests/routes/report.test.js @@ -0,0 +1,139 @@ +/* eslint-disable no-undef */ +/* eslint-disable import/no-extraneous-dependencies */ +import chai from 'chai'; +import chaiHttp from 'chai-http'; +import jwt from 'jsonwebtoken'; +import * as Factory from '../../helpers/factory'; +import status from '../../config/status'; +import db from '../../models'; +import app from '../../app'; + +chai.use(chaiHttp); +chai.should(); +let accessToken; +let createdUser = {}; +let createdArticle = {}; + +let reportId; + +const newUser = Factory.user.build(); +const newArticle = Factory.article.build(); +const newReport = Factory.report.build(); + +delete newUser.id; +delete newArticle.id; +delete newReport.id; + +describe('Reports', () => { + before(async () => { + try { + await db.User.destroy({ + truncate: true, + cascade: true, + logging: false + }); + await db.Article.destroy({ + truncate: true, + cascade: true, + logging: false + }); + await db.Report.destroy({ + truncate: true, + cascade: true, + logging: false + }); + + createdUser = (await db.User.create(newUser, { logging: false })).dataValues; + newArticle.userId = createdUser.id; + accessToken = jwt.sign( + { id: createdUser.id, role: createdUser.role, permissions: createdUser.permissions }, + process.env.SECRET_KEY, + { expiresIn: '1d' } + ); + createdArticle = await db.Article.create(newArticle, { logging: false }); + } catch (err) { + throw err; + } + }); + it('should let user create report', (done) => { + chai + .request(app) + .post(`/api/v1/article/${createdArticle.slug}/report`) + .set('access-token', accessToken) + .send(newReport) + .end((err, res) => { + reportId = res.body.report.id; + res.should.have.status(status.CREATED); + done(); + }); + }); + it('should not let user create report with wrong input', (done) => { + newReport.title = ''; + chai + .request(app) + .post(`/api/v1/article/${createdArticle.slug}/report`) + .set('access-token', accessToken) + .send(newReport) + .end((err, res) => { + res.should.have.status(status.BAD_REQUEST); + done(); + }); + }); + + it('should not let user create report more than once', (done) => { + chai + .request(app) + .post(`/api/v1/article/${createdArticle.slug}/report`) + .set('access-token', accessToken) + .send({ ...newReport, title: 'new title' }) + .end((err, res) => { + res.should.have.status(status.EXIST); + done(); + }); + }); + + it('should let the user get the existing report', (done) => { + chai + .request(app) + .get(`/api/v1/article/${createdArticle.slug}/report/${reportId}`) + .set('access-token', accessToken) + .send() + .end((err, res) => { + res.should.have.status(status.OK); + done(); + }); + }); + it('should let user get all existing reports', (done) => { + chai + .request(app) + .get(`/api/v1/article/${createdArticle.slug}/report`) + .set('access-token', accessToken) + .send() + .end((err, res) => { + res.should.have.status(status.OK); + done(); + }); + }); + it('should not let the user delete a report that does not exist', (done) => { + chai + .request(app) + .delete(`/api/v1/article/${createdArticle.slug}/report/8372736`) + .set('access-token', accessToken) + .send() + .end((err, res) => { + res.should.have.status(status.BAD_REQUEST); + done(); + }); + }); + it('should not let the user delete a report that does not exist', (done) => { + chai + .request(app) + .delete(`/api/v1/article/${createdArticle.slug}/report/${reportId}`) + .set('access-token', accessToken) + .send() + .end((err, res) => { + res.should.have.status(status.OK); + done(); + }); + }); +}); diff --git a/src/tests/routes/roles.test.js b/src/tests/routes/roles.test.js new file mode 100644 index 00000000..524dbf48 --- /dev/null +++ b/src/tests/routes/roles.test.js @@ -0,0 +1,100 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import chai from 'chai'; +import jwt from 'jsonwebtoken'; +import dotenv from 'dotenv'; +import db from '../../models'; +import * as permissions from '../../config/permissions'; +import status from '../../config/status'; +import * as Factory from '../../helpers/factory'; +import app from '../../app'; + +dotenv.config(); + +const { expect } = chai; + +let accessTokenAdmin = ''; +let createdUserAdmin = ''; +let createdUserNormal = ''; + +const userAdmin = Factory.userAdmin.build(); +const userNormal = Factory.userNormal.build(); +delete userAdmin.id; +delete userNormal.id; +describe('Roles and Access control', () => { + before(async () => { + try { + await db.User.destroy({ + truncate: true, + cascade: true, + logging: false + }); + + createdUserAdmin = (await db.User.create(userAdmin, { logging: false })).dataValues; + createdUserNormal = (await db.User.create(userNormal, { logging: false })).dataValues; + accessTokenAdmin = jwt.sign( + { + id: createdUserAdmin.id, + role: createdUserAdmin.role, + permissions: createdUserAdmin.permissions + }, + process.env.SECRET_KEY, + { expiresIn: '1d' } + ); + } catch (error) { + return error; + } + }); + + it('return 404 if user does not exist', (done) => { + chai + .request(app) + .put('/api/v1/users/roles/deschamps') + .set('access-token', accessTokenAdmin) + .send({ role: 'admin' }) + .end((err, res) => { + expect(res.status).to.be.equal(status.NOT_FOUND); + expect(res.body).to.not.include.keys('errors'); + done(); + }); + }); + + it('successfully changes a user role', (done) => { + const requestBody = { + role: 'admin' + }; + chai + .request(app) + .put(`/api/v1/users/roles/${createdUserNormal.username}`) + .set('access-token', accessTokenAdmin) + .send(requestBody) + .end((err, res) => { + expect(res.status).to.be.equal(status.OK); + expect(res.body.message).to.eql('roles updated successfully'); + done(); + }); + }); + + it('should return EXIST if the user is already an admin', (done) => { + chai + .request(app) + .put(`/api/v1/users/roles/${createdUserNormal.username}`) + .set('access-token', accessTokenAdmin) + .send({ role: 'admin' }) + .end((err, res) => { + expect(res.status).to.be.equal(status.EXIST); + done(); + }); + }); + + it('should return BAD REQUEST when the request body is empty', (done) => { + chai + .request(app) + .put(`/api/v1/users/roles/${createdUserNormal.username}`) + .set('access-token', accessTokenAdmin) + .send('') + .end((err, res) => { + expect(res.status).to.be.equal(status.BAD_REQUEST); + done(); + }); + }); +}); diff --git a/src/tests/routes/userArticles.test.js b/src/tests/routes/userArticles.test.js new file mode 100644 index 00000000..746d216f --- /dev/null +++ b/src/tests/routes/userArticles.test.js @@ -0,0 +1,123 @@ +import chai from 'chai'; +import chaiHttp from 'chai-http'; +import jwt from 'jsonwebtoken'; +import app from '../../app'; +import status from '../../config/status'; +import db from '../../models'; +import * as Factory from '../../helpers/factory'; + +const { expect } = chai; +chai.should(); +chai.use(chaiHttp); + +let accessToken = ''; +// fake article 1 +const article1 = Factory.article.build(); +// fake article 2 +const article2 = Factory.article.build(); +// fake user +const user = Factory.user.build(); + +let createdUser = ''; +delete user.id; +user.email = 'wayde@haven.com'; +user.username = 'wayde123'; +describe('USER ARTICLES', () => { + before(async () => { + // create a user + // create a user + createdUser = (await db.User.create(user, { logging: false })).dataValues; + article1.userId = createdUser.id; + article2.userId = createdUser.id; + // generate token + accessToken = jwt.sign( + { id: createdUser.id, role: createdUser.role, permissions: createdUser.permissions }, + process.env.SECRET_KEY, + { expiresIn: '1d' } + ); + }); + describe('NO ARTICLES', () => { + it('Should not get draft articles if a user does not have one', (done) => { + chai + .request(app) + .get('/api/v1/articles/drafts') + .set('access-token', accessToken) + .end((err, res) => { + expect(res).to.have.status(status.NOT_FOUND); + res.body.message.should.be.an('string'); + res.body.message.should.be.equal('No articles found'); + done(); + }); + }); + it('Should not get published articles if a user does not have one', (done) => { + chai + .request(app) + .get('/api/v1/articles/published') + .set('access-token', accessToken) + .end((err, res) => { + expect(res).to.have.status(status.NOT_FOUND); + res.body.message.should.be.an('string'); + res.body.message.should.be.equal('No articles found'); + done(); + }); + }); + }); + describe('ARTICLES', () => { + let createdArticle2 = ''; + before(async () => { + // create article 1 + delete article1.id; + delete article1.readTime; + article1.slug = 'baller-wayde-hasli23f1'; + article1.userId = createdUser.id; + await db.Article.create(article1, { logging: false }); + // create article 2 + delete article1.id; + delete article1.readTime; + article2.slug = 'sometimes-i-see-magict-yaw42i2f1'; + article2.userId = createdUser.id; + createdArticle2 = await db.Article.create(article2, { logging: false }); + // publish article 2 + await db.Article.update( + { status: 'published' }, + { where: { slug: createdArticle2.dataValues.slug } } + ); + }); + it('Should get draft articles', (done) => { + chai + .request(app) + .get('/api/v1/articles/drafts') + .set('access-token', accessToken) + .end((err, res) => { + expect(res).to.have.status(status.OK); + res.body.articles.should.be.a('array'); + res.body.articles[0].should.be.a('object'); + res.body.articles[0].id.should.be.a('number'); + res.body.articles[0].title.should.be.a('string'); + res.body.articles[0].body.should.be.a('string'); + res.body.articles[0].description.should.be.a('string'); + res.body.articles[0].slug.should.be.a('string'); + res.body.articles[0].coverUrl.should.be.a('string'); + done(); + }); + }); + it('Should get published articles', (done) => { + chai + .request(app) + .get('/api/v1/articles/published') + .set('access-token', accessToken) + .end((err, res) => { + expect(res).to.have.status(status.OK); + res.body.articles.should.be.a('array'); + res.body.articles[0].should.be.a('object'); + res.body.articles[0].id.should.be.a('number'); + res.body.articles[0].title.should.be.a('string'); + res.body.articles[0].body.should.be.a('string'); + res.body.articles[0].description.should.be.a('string'); + res.body.articles[0].slug.should.be.a('string'); + res.body.articles[0].coverUrl.should.be.a('string'); + done(); + }); + }); + }); +}); diff --git a/src/tests/routes/users.test.js b/src/tests/routes/users.test.js new file mode 100644 index 00000000..79ac0ef7 --- /dev/null +++ b/src/tests/routes/users.test.js @@ -0,0 +1,325 @@ +/* eslint-disable no-unused-expressions */ +/* eslint-disable import/no-extraneous-dependencies */ +import chai from 'chai'; +import jwt from 'jsonwebtoken'; +import dotenv from 'dotenv'; +import db from '../../models'; +import status from '../../config/status'; +import * as Factory from '../../helpers/factory'; +import app from '../../app'; + +dotenv.config(); + +const { expect } = chai; + +let accessTokenAdmin = ''; +let accessTokenNormalUser = ''; +let createdUserOne = ''; +let createdUserTwo = ''; + +const userOne = Factory.user.build(); +const userTwo = Factory.user.build(); + +delete userOne.id; +delete userTwo.id; + +userTwo.email = 'aaa@bbb.ccc'; +userTwo.username = 'aaabbb'; + +describe('Users routes', () => { + before(async () => { + try { + await db.User.destroy({ + truncate: true, + cascade: true, + logging: false + }); + createdUserOne = (await db.User.create(userOne, { logging: false })).dataValues; + createdUserTwo = (await db.User.create(userTwo, { logging: false })).dataValues; + accessTokenAdmin = jwt.sign( + { + id: createdUserOne.id, + role: createdUserOne.role, + permissions: createdUserOne.permissions + }, + process.env.SECRET_KEY, + { expiresIn: '1d' } + ); + accessTokenNormalUser = jwt.sign( + { + id: createdUserOne.id, + role: 'normal', + permissions: createdUserOne.permissions + }, + process.env.SECRET_KEY, + { expiresIn: '1d' } + ); + } catch (error) { + throw error; + } + }); + + it('should not get user information', (done) => { + chai + .request(app) + .get('/api/v1/auth/1') + .set('access-token', 'aaa') + .end((err, res) => { + expect(res.status).to.be.equal(status.UNAUTHORIZED); + done(); + }); + }); + + it('should get user information', (done) => { + chai + .request(app) + .get(`/api/v1/auth/${createdUserOne.id}`) + .set('access-token', accessTokenAdmin) + .end((err, res) => { + expect(res.status).to.be.equal(status.OK); + done(); + }); + }); + + // activate user account + it('should activate user account', (done) => { + const userTwoAccessToken = jwt.sign( + { + email: createdUserOne.email, + expiresIn: '2h' + }, + process.env.SECRET_KEY, + { expiresIn: '1d' } + ); + chai + .request(app) + .get(`/api/v1/auth/activate/${userTwoAccessToken}`) + .set('access-token', userTwoAccessToken) + .end((err, res) => { + chai.expect(res).to.redirect; + done(); + }); + }); + // activate account for user two + it('should activate user account', (done) => { + const userAccessToken = jwt.sign( + { + email: createdUserTwo.email, + expiresIn: '2h' + }, + process.env.SECRET_KEY, + { expiresIn: '1d' } + ); + chai + .request(app) + .get(`/api/v1/auth/activate/${userAccessToken}`) + .set('access-token', userAccessToken) + .end((err, res) => { + chai.expect(res).to.redirect; + done(); + }); + }); + // update user profile + it('should update the user profile', (done) => { + chai + .request(app) + .put('/api/v1/users') + .send({ username: 'anotherusername', password: 'Abcd1234!', email: 'email@email.com' }) + .set('access-token', accessTokenNormalUser) + .end((err, res) => { + expect(res.status).to.be.equal(status.OK); + expect(res.body).to.include.keys('user'); + expect(res.body).to.not.include.keys('errors'); + done(); + }); + }); + it('should not update the user profile if the email is already used', (done) => { + chai + .request(app) + .put('/api/v1/users') + .send({ email: createdUserTwo.email }) + .set('access-token', accessTokenNormalUser) + .end((err, res) => { + expect(res.status).to.be.equal(status.EXIST); + expect(res.body).to.not.include.keys('user'); + expect(res.body).to.include.keys('errors'); + done(); + }); + }); + it('should update the user profile', (done) => { + chai + .request(app) + .put(`/api/v1/users/${createdUserOne.id}`) + .send({ password: 'Abcd1234!!', permissions: JSON.parse(createdUserOne.permissions) }) + .set('access-token', accessTokenAdmin) + .end((err, res) => { + expect(res.status).to.be.equal(status.OK); + expect(res.body).to.include.keys('user'); + expect(res.body).to.not.include.keys('errors'); + done(); + }); + }); + + it('should not update the user profile if the user is not authenticated', (done) => { + chai + .request(app) + .put('/api/v1/users') + .send({ username: 'anotherusername', password: 'Abcd1234!', email: 'aaa@bbb.com' }) + .end((err, res) => { + expect(res.status).to.be.equal(status.UNAUTHORIZED); + done(); + }); + }); + + it('should not update the user profile if the user is not an admin', (done) => { + chai + .request(app) + .put(`/api/v1/users/${createdUserTwo.id}`) + .send({ role: 'admin', permissions: {} }) + .set('access-token', accessTokenNormalUser) + .end((err, res) => { + expect(res.status).to.be.equal(status.UNAUTHORIZED); + done(); + }); + }); + + it('should not update the user profile if there is a user with the same email', (done) => { + chai + .request(app) + .put(`/api/v1/users/${createdUserTwo.id}`) + .send({ email: createdUserOne.email }) + .set('access-token', accessTokenAdmin) + .end((err, res) => { + expect(res.status).to.be.equal(status.EXIST); + done(); + }); + }); + + it('should not update the user profile if there is a user with the same username', (done) => { + chai + .request(app) + .put(`/api/v1/users/${createdUserTwo.id}`) + .send({ username: 'anotherusername' }) + .set('access-token', accessTokenAdmin) + .end((err, res) => { + expect(res.status).to.be.equal(status.EXIST); + done(); + }); + }); + + it('should not update the user profile if some inputs are malformed', (done) => { + chai + .request(app) + .put('/api/v1/users') + .send({ username: '' }) + .set('access-token', accessTokenAdmin) + .end((err, res) => { + expect(res.status).to.be.equal(status.BAD_REQUEST); + done(); + }); + }); + + it('should not update the user profile if some inputs are malformed', (done) => { + chai + .request(app) + .put('/api/v1/users') + .send({ password: 'abcd1234!', email: 'aaa@bbb' }) + .set('access-token', accessTokenAdmin) + .end((err, res) => { + expect(res.status).to.be.equal(status.BAD_REQUEST); + done(); + }); + }); + + it('should not update the user profile if the body is empty', (done) => { + chai + .request(app) + .put('/api/v1/users') + .send({}) + .set('access-token', accessTokenAdmin) + .end((err, res) => { + expect(res.status).to.be.equal(status.BAD_REQUEST); + done(); + }); + }); + + it('should not update user email if the token is not valid', (done) => { + chai + .request(app) + .get('/api/v1/users/email/confirm/invalid-token') + .set('access-token', accessTokenNormalUser) + .end((err, res) => { + expect(res).to.redirect; + expect(res.redirects[0].indexOf(`token=${status.UNAUTHORIZED}`)).to.be.above(0); + done(); + }); + }); + + it('should update user email if the token is valid', (done) => { + const token = jwt.sign( + { + userId: createdUserOne.id, + email: 'new@email.com' + }, + process.env.SECRET_KEY, + { expiresIn: '1d' } + ); + chai + .request(app) + .get(`/api/v1/users/email/confirm/${token}`) + .set('access-token', accessTokenNormalUser) + .end((err, res) => { + expect(res).to.redirect; + expect(res.redirects[0].indexOf('email=new@email.com')).to.be.above(0); + done(); + }); + }); + + it('should fetch one user by id', (done) => { + chai + .request(app) + .get(`/api/v1/users/${createdUserTwo.id}`) + .set('access-token', accessTokenAdmin) + .end((err, res) => { + expect(res.status).to.be.equal(status.OK); + done(); + }); + }); + it('should not fetch one user by if id is wrong', (done) => { + const id = 0; + chai + .request(app) + .get(`/api/v1/users/${id}`) + .set('access-token', accessTokenAdmin) + .end((err, res) => { + expect(res.status).to.be.equal(status.NOT_FOUND); + done(); + }); + }); + + it('return all users whose username include the provided characters', (done) => { + chai + .request(app) + .get(`/api/v1/users/username/${createdUserTwo.username}?limit=1&offset=0`) + .set('access-token', accessTokenAdmin) + .end((err, res) => { + res.body.should.be.an('object'); + expect(res.body).to.include.keys('users'); + expect(res.body.users.length).to.be.greaterThan(0); + res.status.should.be.equal(status.OK); + done(); + }); + }); + + it('should not return all users if no user with the provided username is found ', (done) => { + chai + .request(app) + .get('/api/v1/users/username/fake-username?limit=1&offset=0') + .set('access-token', accessTokenAdmin) + .end((err, res) => { + res.body.should.be.an('object'); + res.status.should.be.equal(status.NOT_FOUND); + done(); + }); + }); +}); diff --git a/swagger.json b/swagger.json new file mode 100644 index 00000000..a5e65d74 --- /dev/null +++ b/swagger.json @@ -0,0 +1,1922 @@ +{ + "swagger": "2.0", + "info": { + "description": "A Social platform for the creative at heart", + "version": "1.0.0", + "title": "Authors Haven", + "license": { + "name": "Apache 2.0", + "url": "http://www.apache.org/licenses/LICENSE-2.0.html" + } + }, + "host": "ninjas-ah-backend.herokuapp.com", + "basePath": "/api/v1", + "tags": [ + { + "name": "auth", + "description": "Users Authentication" + }, + { + "name": "users", + "description": "Operations about users" + }, + { + "name": "articles", + "description": "Operations about articles" + }, + { + "name": "notifications", + "description": "Operations about notifications" + }, + { + "name": "chats", + "description": "Operations about chats" + } + ], + "schemes": ["https", "http"], + "paths": { + "/auth/login": { + "post": { + "tags": ["auth"], + "summary": "Authenticate a user", + "description": "Authenticate a user and send a token", + "operationId": "Login", + "consumes": ["application/json"], + "produces": ["application/json"], + "parameters": [ + { + "in": "body", + "name": "body", + "description": "Credentials to be sent to authenticate a user", + "required": true, + "schema": { + "type": "object", + "required": ["email", "password"], + "properties": { + "email": { + "type": "string", + "example": "user@domain.com" + }, + "password": { + "type": "string", + "example": "Abcd1234!" + } + } + } + } + ], + "responses": { + "200": { + "description": "Successfully authenticated" + }, + "400": { + "description": "Bad request" + }, + "404": { + "description": "User not found" + }, + "500": { + "description": "Server error" + } + } + } + }, + "/auth/signup": { + "post": { + "tags": ["auth"], + "summary": "Register a user", + "description": "Register a new user and send a activation email", + "operationId": "Signup", + "consumes": ["application/json"], + "produces": ["application/json"], + "parameters": [ + { + "in": "body", + "name": "body", + "description": "Information to be sent to register a new user", + "required": true, + "schema": { + "type": "object", + "required": [ + "firstName", + "lastName", + "username", + "email", + "password" + ], + "properties": { + "firstName": { + "type": "string" + }, + "lastName": { + "type": "string" + }, + "username": { + "type": "string" + }, + "email": { + "type": "string" + }, + "password": { + "type": "string" + } + } + } + } + ], + "responses": { + "201": { + "description": "Account Successfully created" + }, + "400": { + "description": "Bad request" + }, + "409": { + "description": "User already exist" + }, + "500": { + "description": "Server error" + } + } + } + }, + "/auth/facebook": { + "get": { + "tags": ["auth"], + "summary": "Register/login a facebook user", + "description": "Register/login a facebook user and fetch the user profile", + "operationId": "Facebook", + "produces": ["application/json"], + "responses": { + "200": { + "description": "User successfully authenticated" + }, + "201": { + "description": "Account Successfully created" + }, + "409": { + "description": "User alredy exist" + }, + "500": { + "description": "Server error" + } + } + } + }, + "/auth/google": { + "get": { + "tags": ["auth"], + "summary": "Register/login a google user", + "description": "Register/login a google user and fetch the user profile", + "operationId": "Google", + "produces": ["application/json"], + "responses": { + "200": { + "description": "User successfully authenticated" + }, + "201": { + "description": "Account Successfully created" + }, + "409": { + "description": "User alredy exist" + }, + "500": { + "description": "Server error" + } + } + } + }, + "/auth/twitter": { + "get": { + "tags": ["auth"], + "summary": "Register/login a twitter user", + "description": "Register/login a twitter user and fetch the user profile", + "operationId": "Twitter", + "produces": ["application/json"], + "responses": { + "200": { + "description": "User successfully authenticated" + }, + "201": { + "description": "Account Successfully created" + }, + "409": { + "description": "User alredy exist" + }, + "500": { + "description": "Server error" + } + } + } + }, + "/auth/logout": { + "get": { + "tags": ["auth"], + "summary": "Logs out current logged in user", + "description": "Blacklist a token", + "operationId": "LogoutUser", + "produces": ["application/json"], + "responses": { + "200": { + "description": "User successfully logged out" + }, + "401": { + "description": "User not authorized to blacklist a given token" + }, + "500": { + "description": "Server error" + } + }, + "security": [ + { + "api_key": [] + } + ] + } + }, + "/users": { + "get": { + "tags": ["users"], + "summary": "Get all users", + "description": "Get users", + "operationId": "GetUsers", + "produces": ["application/json"], + "responses": { + "200": { + "description": "Users successfully fetched" + }, + "401": { + "description": "Not authorized to get users" + }, + "500": { + "description": "Server error" + } + }, + "security": [ + { + "api_key": [] + } + ] + }, + "put": { + "tags": ["users"], + "summary": "Update user profile", + "description": "Update user profile", + "operationId": "UpdateProfile", + "consumes": ["application/json"], + "produces": ["application/json"], + "parameters": [ + { + "in": "body", + "name": "body", + "description": "Information to be sent to update a user profile", + "required": true, + "schema": { + "type": "object", + "properties": { + "firstName": { + "type": "string" + }, + "lastName": { + "type": "string" + }, + "username": { + "type": "string" + }, + "email": { + "type": "string" + }, + "password": { + "type": "string" + }, + "image": { + "type": "string" + } + } + } + } + ], + "responses": { + "200": { + "description": "Account Successfully updated" + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "User not authorized to update a given profile" + }, + "409": { + "description": "User already exist" + }, + "500": { + "description": "Server error" + } + }, + "security": [ + { + "api_key": [] + } + ] + }, + "post": { + "tags": ["users"], + "summary": "Register user", + "description": "Register user profile", + "operationId": "RegisterUser", + "consumes": ["application/json"], + "produces": ["application/json"], + "parameters": [ + { + "in": "body", + "name": "body", + "description": "Information to be sent to register a user profile", + "required": true, + "schema": { + "type": "object", + "properties": { + "firstName": { + "type": "string" + }, + "lastName": { + "type": "string" + }, + "username": { + "type": "string" + }, + "email": { + "type": "string" + }, + "password": { + "type": "string" + }, + "image": { + "type": "string" + }, + "role": { + "type": "string" + }, + "permissions": { + "type": "object", + "example": { + "articles": ["read", "create", "delete", "edit"], + "comments": ["read", "create", "delete", "edit"], + "tags": ["read", "create", "delete"], + "users": ["read", "create", "edit", "delete"] + } + } + } + } + } + ], + "responses": { + "200": { + "description": "Account Successfully created" + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "User not authorized to create an account" + }, + "409": { + "description": "User already exist" + }, + "500": { + "description": "Server error" + } + }, + "security": [ + { + "api_key": [] + } + ] + } + }, + "/users/{id}": { + "get": { + "tags": ["users"], + "summary": "Get user", + "description": "Get user", + "operationId": "GetUser", + "produces": ["application/json"], + "parameters": [ + { + "in": "path", + "name": "id", + "type": "integer", + "required": true + } + ], + "responses": { + "200": { + "description": "User successfully fetched" + }, + "401": { + "description": "Not authorized to get user" + }, + "500": { + "description": "Server error" + } + }, + "security": [ + { + "api_key": [] + } + ] + }, + "put": { + "tags": ["users"], + "summary": "Update user profile", + "description": "Update user profile", + "operationId": "UpdateProfileById", + "consumes": ["application/json"], + "produces": ["application/json"], + "parameters": [ + { + "in": "path", + "name": "id", + "type": "integer", + "required": true + }, + { + "in": "body", + "name": "body", + "description": "Information to be sent to update a user profile", + "required": true, + "schema": { + "type": "object", + "properties": { + "firstName": { + "type": "string" + }, + "lastName": { + "type": "string" + }, + "username": { + "type": "string" + }, + "email": { + "type": "string" + }, + "password": { + "type": "string" + }, + "image": { + "type": "string" + } + } + } + } + ], + "responses": { + "200": { + "description": "Account Successfully updated" + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "User not authorized to update a given profile" + }, + "409": { + "description": "User already exist" + }, + "500": { + "description": "Server error" + } + }, + "security": [ + { + "api_key": [] + } + ] + }, + "delete": { + "tags": ["users"], + "summary": "Delete a user", + "description": "Delete a user", + "operationId": "DeleteUser", + "produces": ["application/json"], + "parameters": [ + { + "in": "path", + "name": "id", + "required": true, + "type": "integer", + "format": "int64" + } + ], + "responses": { + "200": { + "description": "User successfully deleted" + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Not authorized to delete user" + }, + "404": { + "description": "Not found" + }, + "500": { + "description": "Server error" + } + }, + "security": [ + { + "api_key": [] + } + ] + } + }, + "/users/authors": { + "get": { + "tags": ["users"], + "summary": "Get all authors", + "description": "Get all authors", + "operationId": "GetAuthors", + "produces": ["application/json"], + "responses": { + "200": { + "description": "Users successfully fetched" + }, + "401": { + "description": "Not authorized to get users" + }, + "500": { + "description": "Server error" + } + }, + "security": [ + { + "api_key": [] + } + ] + } + }, + "/users/followers": { + "get": { + "tags": ["users"], + "summary": "Get all followers", + "description": "Get all followers of a logged in user", + "operationId": "GetFollowers", + "produces": ["application/json"], + "responses": { + "200": { + "description": "Followers successfully fetched" + }, + "401": { + "description": "Not authorized to get followers" + }, + "500": { + "description": "Server error" + } + }, + "security": [ + { + "api_key": [] + } + ] + } + }, + "/users/following": { + "get": { + "tags": ["users"], + "summary": "Get all followed users", + "description": "Get users followed by a logged in user", + "operationId": "GetFollowing", + "produces": ["application/json"], + "responses": { + "200": { + "description": "Followed successfully fetched" + }, + "401": { + "description": "Not authorized to get followed users" + }, + "500": { + "description": "Server error" + } + }, + "security": [ + { + "api_key": [] + } + ] + } + }, + "/users/{username}/follow": { + "patch": { + "tags": ["users"], + "summary": "Follow user", + "description": "Follow user", + "operationId": "FollowUser", + "produces": ["application/json"], + "parameters": [ + { + "in": "path", + "name": "username", + "type": "string", + "required": true + } + ], + "responses": { + "201": { + "description": "User successfully followed" + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Not authorized to follow user" + }, + "409": { + "description": "User is already followed by the logged in user" + }, + "500": { + "description": "Server error" + } + }, + "security": [ + { + "api_key": [] + } + ] + } + }, + "/users/{username}/unfollow": { + "patch": { + "tags": ["users"], + "summary": "Unfollow user", + "description": "Unfollow user", + "operationId": "UnfollowUser", + "produces": ["application/json"], + "parameters": [ + { + "in": "path", + "name": "username", + "type": "string", + "required": true + } + ], + "responses": { + "200": { + "description": "User successfully unfollowed" + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Not authorized to unfollow user" + }, + "500": { + "description": "Server error" + } + }, + "security": [ + { + "api_key": [] + } + ] + } + }, + "/notifications": { + "get": { + "tags": ["notifications"], + "summary": "Get notifications", + "description": "Get notifications", + "operationId": "GetNotifications", + "produces": ["application/json"], + "responses": { + "200": { + "description": "Notifications successfully fetched" + }, + "401": { + "description": "Not authorized to get notifications" + }, + "500": { + "description": "Server error" + } + }, + "security": [ + { + "api_key": [] + } + ] + } + }, + "/notifications/seen": { + "get": { + "tags": ["notifications"], + "summary": "Get seen notifications", + "description": "Get seen notifications", + "operationId": "GetSeenNotifications", + "produces": ["application/json"], + "responses": { + "200": { + "description": "Notifications successfully fetched" + }, + "401": { + "description": "Not authorized to get notifications" + }, + "500": { + "description": "Server error" + } + }, + "security": [ + { + "api_key": [] + } + ] + } + }, + "/notifications/unseen": { + "get": { + "tags": ["notifications"], + "summary": "Get unseen notifications", + "description": "Get unseen notifications", + "operationId": "GetUnseenNotifications", + "produces": ["application/json"], + "responses": { + "200": { + "description": "Notifications successfully fetched" + }, + "401": { + "description": "Not authorized to get notifications" + }, + "500": { + "description": "Server error" + } + }, + "security": [ + { + "api_key": [] + } + ] + } + }, + "/notifications/{notificationId}": { + "get": { + "tags": ["notifications"], + "summary": "Get a notification", + "description": "Get a notification", + "operationId": "GetSpecificNotification", + "produces": ["application/json"], + "parameters": [ + { + "in": "path", + "name": "notificationId", + "required": true, + "type": "integer", + "format": "int64" + } + ], + "responses": { + "200": { + "description": "Notification successfully fetched" + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Not authorized to get notification" + }, + "500": { + "description": "Server error" + } + }, + "security": [ + { + "api_key": [] + } + ] + }, + "put": { + "tags": ["notifications"], + "summary": "Update a notification", + "description": "Update a notification", + "operationId": "UpdateSpecificNotification", + "consumes": ["application/json"], + "produces": ["application/json"], + "parameters": [ + { + "in": "path", + "name": "notificationId", + "required": true, + "type": "integer", + "format": "int64" + }, + { + "in": "body", + "name": "body", + "required": true, + "schema": { + "type": "object", + "properties": { + "preference": { + "type": "string", + "example": "inApp" + } + } + } + } + ], + "responses": { + "200": { + "description": "Notification successfully updated" + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Not authorized to update notification" + }, + "500": { + "description": "Server error" + } + }, + "security": [ + { + "api_key": [] + } + ] + }, + "delete": { + "tags": ["notifications"], + "summary": "Delete a notification", + "description": "Delete a notification", + "operationId": "DeleteSpecificNotification", + "produces": ["application/json"], + "parameters": [ + { + "in": "path", + "name": "notificationId", + "required": true, + "type": "integer", + "format": "int64" + } + ], + "responses": { + "200": { + "description": "Notification successfully deleted" + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Not authorized to delete notification" + }, + "404": { + "description": "Not found" + }, + "500": { + "description": "Server error" + } + }, + "security": [ + { + "api_key": [] + } + ] + } + }, + "/notifications/{notificationId}/seen": { + "put": { + "tags": ["notifications"], + "summary": "Update a notification status", + "description": "Update a notification", + "operationId": "UpdateSpecificNotificationStatusSeen", + "consumes": ["application/json"], + "produces": ["application/json"], + "parameters": [ + { + "in": "path", + "name": "notificationId", + "required": true, + "type": "integer", + "format": "int64" + }, + { + "in": "body", + "name": "body", + "required": false, + "schema": { + "type": "object", + "properties": { + "preference": { + "type": "string", + "example": "inApp" + } + } + } + } + ], + "responses": { + "200": { + "description": "Notification successfully updated" + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Not authorized to update notification" + }, + "500": { + "description": "Server error" + } + }, + "security": [ + { + "api_key": [] + } + ] + } + }, + "/notifications/{notificationId}/unseen": { + "put": { + "tags": ["notifications"], + "summary": "Update a notification status", + "description": "Update a notification", + "operationId": "UpdateSpecificNotificationStatusUnseen", + "consumes": ["application/json"], + "produces": ["application/json"], + "parameters": [ + { + "in": "path", + "name": "notificationId", + "required": true, + "type": "integer", + "format": "int64" + }, + { + "in": "body", + "name": "body", + "required": false, + "schema": { + "type": "object", + "properties": { + "preference": { + "type": "string", + "example": "inApp" + } + } + } + } + ], + "responses": { + "200": { + "description": "Notification successfully updated" + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Not authorized to update notification" + }, + "500": { + "description": "Server error" + } + }, + "security": [ + { + "api_key": [] + } + ] + } + }, + "/notifications/configuration": { + "post": { + "tags": ["notifications"], + "summary": "Create notification configuration", + "description": "Create notification configuration", + "operationId": "CreateNotificationConfiguration", + "consumes": ["application/json"], + "produces": ["application/json"], + "parameters": [ + { + "in": "body", + "name": "body", + "description": "Information to be sent to configure notification", + "required": true, + "schema": { + "$ref": "#/definitions/NotificationConfig" + } + } + ], + "responses": { + "201": { + "description": "Configuration successfully set" + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Not authorized to set notification configuration" + }, + "409": { + "description": "Configuration already set" + }, + "500": { + "description": "Server error" + } + }, + "security": [ + { + "api_key": [] + } + ] + }, + "put": { + "tags": ["notifications"], + "summary": "Update notification configuration", + "description": "Update notification configuration", + "operationId": "UpdateNotificationConfiguration", + "consumes": ["application/json"], + "produces": ["application/json"], + "parameters": [ + { + "in": "body", + "name": "body", + "description": "Information to be sent to update notification configuration", + "required": true, + "schema": { + "$ref": "#/definitions/NotificationConfig" + } + } + ], + "responses": { + "200": { + "description": "Configuration successfully updated" + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Not authorized to set notification configuration" + }, + "409": { + "description": "Configuration already set" + }, + "500": { + "description": "Server error" + } + }, + "security": [ + { + "api_key": [] + } + ] + }, + "get": { + "tags": ["notifications"], + "summary": "Get notification configuration", + "description": "Get notification configuration", + "operationId": "GetNotificationConfiguration", + "produces": ["application/json"], + "responses": { + "200": { + "description": "Configuration successfully fetched" + }, + "401": { + "description": "Not authorized to get notification configuration" + }, + "500": { + "description": "Server error" + } + }, + "security": [ + { + "api_key": [] + } + ] + } + }, + "/chats": { + "post": { + "tags": ["chats"], + "summary": "Create a chat", + "description": "Create a chat", + "operationId": "CreateChat", + "consumes": ["application/json"], + "produces": ["application/json"], + "parameters": [ + { + "in": "body", + "name": "body", + "description": "Message to be sent", + "required": true, + "schema": { + "type": "object", + "required": ["message"], + "properties": { + "message": { + "type": "string", + "example": "Hello" + } + } + } + } + ], + "responses": { + "201": { + "description": "Message successfully sent" + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Not authorized to send message" + }, + "500": { + "description": "Server error" + } + }, + "security": [ + { + "api_key": [] + } + ] + }, + "get": { + "tags": ["chats"], + "summary": "Get chats", + "description": "Get chats", + "operationId": "GetChats", + "produces": ["application/json"], + "responses": { + "200": { + "description": "Chats successfully fetched" + }, + "401": { + "description": "Not authorized to get chats" + }, + "500": { + "description": "Server error" + } + }, + "security": [ + { + "api_key": [] + } + ] + } + }, + "/chats/{chatId}": { + "delete": { + "tags": ["chats"], + "summary": "Delete a chat", + "description": "Delete a chat", + "operationId": "DeleteChat", + "produces": ["application/json"], + "parameters": [ + { + "in": "path", + "name": "chatId", + "required": true, + "type": "integer", + "format": "int64" + } + ], + "responses": { + "200": { + "description": "Chat successfully deleted" + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Not authorized to delete chat" + }, + "404": { + "description": "Not found" + }, + "500": { + "description": "Server error" + } + }, + "security": [ + { + "api_key": [] + } + ] + } + }, + "/articles": { + "get": { + "tags": ["articles"], + "summary": "Get articles", + "description": "Get articles", + "operationId": "GetArticles", + "produces": ["application/json"], + "responses": { + "200": { + "description": "Articles successfully fetched" + }, + "500": { + "description": "Server error" + } + } + }, + "post": { + "tags": ["articles"], + "summary": "Create an article", + "description": "Create an article", + "operationId": "CreateArticle", + "consumes": ["application/json"], + "produces": ["application/json"], + "parameters": [ + { + "in": "body", + "name": "body", + "description": "Information to be sent to create an article", + "required": true, + "schema": { + "type": "object", + "required": ["title", "description", "body"], + "properties": { + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "body": { + "type": "string" + }, + "coverUrl": { + "type": "string" + } + } + } + } + ], + "responses": { + "201": { + "description": "Article successfully created" + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Not authorized to create an article" + }, + "500": { + "description": "Server error" + } + }, + "security": [ + { + "api_key": [] + } + ] + } + }, + "/articles/{slug}": { + "get": { + "tags": ["articles"], + "summary": "Get articles", + "description": "Get articles", + "operationId": "GetArticle", + "produces": ["application/json"], + "parameters": [ + { + "in": "path", + "name": "slug", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Article successfully fetched" + }, + "404": { + "description": "Article not found" + }, + "500": { + "description": "Server error" + } + } + }, + "put": { + "tags": ["articles"], + "summary": "Update an article", + "description": "Update an article", + "operationId": "UpdateArticle", + "consumes": ["application/json"], + "produces": ["application/json"], + "parameters": [ + { + "in": "path", + "name": "slug", + "required": true, + "type": "string" + }, + { + "in": "body", + "name": "body", + "description": "Information to be sent to create an article", + "required": true, + "schema": { + "type": "object", + "required": ["title", "description", "body"], + "properties": { + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "body": { + "type": "string" + } + } + } + } + ], + "responses": { + "200": { + "description": "Article successfully updated" + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Not authorized to update an article" + }, + "500": { + "description": "Server error" + } + }, + "security": [ + { + "api_key": [] + } + ] + }, + "delete": { + "tags": ["articles"], + "summary": "Delete article", + "description": "Delete article", + "operationId": "DeleteArticle", + "produces": ["application/json"], + "parameters": [ + { + "in": "path", + "name": "slug", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Article successfully deleted" + }, + "401": { + "description": "Not authorized to delete article" + }, + "404": { + "description": "Article not found" + }, + "500": { + "description": "Server error" + } + } + } + }, + "/articles/{slug}/publish": { + "put": { + "tags": ["articles"], + "summary": "Publish an article", + "description": "Publish an article", + "operationId": "PublishArticle", + "produces": ["application/json"], + "parameters": [ + { + "in": "path", + "name": "slug", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Article successfully published" + }, + "401": { + "description": "Not authorized to publish article" + }, + "404": { + "description": "Article not found" + }, + "500": { + "description": "Server error" + } + }, + "security": [ + { + "api_key": [] + } + ] + } + }, + "/articles/{slug}/unpublish": { + "put": { + "tags": ["articles"], + "summary": "Unpublish an article", + "description": "Unpublish an article", + "operationId": "UnpublishArticle", + "produces": ["application/json"], + "parameters": [ + { + "in": "path", + "name": "slug", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Article successfully unpublished" + }, + "401": { + "description": "Not authorized to unpublish article" + }, + "404": { + "description": "Article not found" + }, + "500": { + "description": "Server error" + } + }, + "security": [ + { + "api_key": [] + } + ] + } + }, + "/articles/drafts": { + "get": { + "tags": ["articles"], + "summary": "Get drafts", + "description": "Get articles that have draft status", + "operationId": "GetDraftsArticles", + "produces": ["application/json"], + "responses": { + "200": { + "description": "Drafts successfully fetched" + }, + "401": { + "description": "Not authorized" + }, + "500": { + "description": "Server error" + } + }, + "security": [ + { + "api_key": [] + } + ] + } + }, + "/articles/published": { + "get": { + "tags": ["articles"], + "summary": "Get published articles", + "description": "Get published articles", + "operationId": "GetPublishedArticles", + "produces": ["application/json"], + "responses": { + "200": { + "description": "Published articles successfully fetched" + }, + "401": { + "description": "Not authorized" + }, + "500": { + "description": "Server error" + } + }, + "security": [ + { + "api_key": [] + } + ] + } + }, + "/articles/bookmarked": { + "get": { + "tags": ["articles"], + "summary": "Get bookmarked articles", + "description": "Get bookmarked articles", + "operationId": "GetBookmarkedArticles", + "produces": ["application/json"], + "responses": { + "200": { + "description": "Bookmarked articles successfully fetched" + }, + "401": { + "description": "Not authorized" + }, + "500": { + "description": "Server error" + } + }, + "security": [ + { + "api_key": [] + } + ] + } + }, + "/articles/{slug}/bookmark": { + "patch": { + "tags": ["articles"], + "summary": "Bookmark an article", + "description": "Bookmark an article", + "operationId": "BookmarkArticle", + "produces": ["application/json"], + "parameters": [ + { + "in": "path", + "name": "slug", + "required": true, + "type": "string" + } + ], + "responses": { + "201": { + "description": "Article successfully bookmarked" + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Article not found" + }, + "409": { + "description": "Article already bookmarked" + }, + "500": { + "description": "Server error" + } + }, + "security": [ + { + "api_key": [] + } + ] + }, + "delete": { + "tags": ["articles"], + "summary": "Remove article from bookmarks", + "description": "Remove article from bookmarks", + "operationId": "RemoveBookmarkArticle", + "produces": ["application/json"], + "parameters": [ + { + "in": "path", + "name": "slug", + "required": true, + "type": "string" + } + ], + "responses": { + "201": { + "description": "Article successfully removed from bookmarks" + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Article not found" + }, + "500": { + "description": "Server error" + } + }, + "security": [ + { + "api_key": [] + } + ] + } + }, + "/articles/favorited": { + "get": { + "tags": ["articles"], + "summary": "Get favorited articles", + "description": "Get favorited articles", + "operationId": "GetFavoritedArticles", + "produces": ["application/json"], + "responses": { + "200": { + "description": "Favorited articles successfully fetched" + }, + "401": { + "description": "Not authorized" + }, + "500": { + "description": "Server error" + } + }, + "security": [ + { + "api_key": [] + } + ] + } + }, + "/articles/{slug}/favorite": { + "patch": { + "tags": ["articles"], + "summary": "Favorite an article", + "description": "Favorite an article", + "operationId": "FavoriteArticle", + "produces": ["application/json"], + "parameters": [ + { + "in": "path", + "name": "slug", + "required": true, + "type": "string" + } + ], + "responses": { + "201": { + "description": "Article successfully favorited" + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Article not found" + }, + "409": { + "description": "Article already favorited" + }, + "500": { + "description": "Server error" + } + }, + "security": [ + { + "api_key": [] + } + ] + }, + "delete": { + "tags": ["articles"], + "summary": "Remove article from favorites", + "description": "Remove article from favorites", + "operationId": "RemoveFavoriteArticle", + "produces": ["application/json"], + "parameters": [ + { + "in": "path", + "name": "slug", + "required": true, + "type": "string" + } + ], + "responses": { + "201": { + "description": "Article successfully removed from favorites" + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Article not found" + }, + "500": { + "description": "Server error" + } + }, + "security": [ + { + "api_key": [] + } + ] + } + }, + "/articles/{slug}/share/facebook": { + "get": { + "tags": ["articles"], + "summary": "Share an article on facebook", + "description": "Share an article on facebook", + "operationId": "ShareArticleFacebook", + "produces": ["application/json"], + "parameters": [ + { + "in": "path", + "name": "slug", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Article successfully fetched" + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Article not found" + }, + "500": { + "description": "Server error" + } + }, + "security": [ + { + "api_key": [] + } + ] + } + }, + "/articles/{slug}/share/twitter": { + "get": { + "tags": ["articles"], + "summary": "Share an article on twitter", + "description": "Share an article on twitter", + "operationId": "ShareArticleTwitter", + "produces": ["application/json"], + "parameters": [ + { + "in": "path", + "name": "slug", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Article successfully fetched" + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Article not found" + }, + "500": { + "description": "Server error" + } + }, + "security": [ + { + "api_key": [] + } + ] + } + }, + "/articles/{slug}/share/gmail": { + "get": { + "tags": ["articles"], + "summary": "Share an article on gmail", + "description": "Share an article on gmail", + "operationId": "ShareArticleGmail", + "produces": ["application/json"], + "parameters": [ + { + "in": "path", + "name": "slug", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Article successfully fetched" + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Article not found" + }, + "500": { + "description": "Server error" + } + }, + "security": [ + { + "api_key": [] + } + ] + } + }, + "/articles/{slug}/share/likedin": { + "get": { + "tags": ["articles"], + "summary": "Share an article on likedin", + "description": "Share an article on likedin", + "operationId": "ShareArticleLikedin", + "produces": ["application/json"], + "parameters": [ + { + "in": "path", + "name": "slug", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Article successfully fetched" + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Article not found" + }, + "500": { + "description": "Server error" + } + }, + "security": [ + { + "api_key": [] + } + ] + } + } + }, + "securityDefinitions": { + "api_key": { + "type": "apiKey", + "name": "access-token", + "in": "header" + } + }, + "definitions": { + "NotificationConfig": { + "type": "object", + "properties": { + "config": { + "type": "object", + "properties": { + "inApp": { + "type": "object", + "properties": { + "articles": { + "type": "object", + "properties": { + "show": { + "type": "boolean", + "example": true + }, + "on": { + "type": "array", + "items": { + "type": "string" + }, + "example": ["publish", "comment", "like"] + } + } + } + } + }, + "email": { + "type": "object", + "properties": { + "articles": { + "type": "object", + "properties": { + "show": { + "type": "boolean", + "example": true + }, + "on": { + "type": "array", + "items": { + "type": "string" + }, + "example": ["publish", "comment", "like"] + } + } + } + } + } + } + } + } + } + }, + "externalDocs": { + "description": "Find out more about Swagger", + "url": "http://swagger.io" + } +} diff --git a/templates/css/style.css b/templates/css/style.css new file mode 100644 index 00000000..fdf9b38f --- /dev/null +++ b/templates/css/style.css @@ -0,0 +1,386 @@ +.xxlarge-text { + font-size: 40px; } + +.xlarge-text { + font-size: 32px; } + +.large-text { + font-size: 24px; } + +.medium-text { + font-size: 16px; } + +.small-text { + font-size: 12px; } + +h1, h2, h3, h4, h5 { + margin: 6px 0; } + +/* row */ +.row { + margin-left: 0rem; + margin-right: 0rem; } + +/* container */ +.container { + margin: 0 auto; + max-width: 1100px; + width: 98%; } + +/* grid */ +.small-screen-1, .medium-screen-1, .large-screen-1, .small-screen-2, .medium-screen-2, .large-screen-2, .small-screen-3, .medium-screen-3, .large-screen-3, .small-screen-4, .medium-screen-4, .large-screen-4 { + margin: 0 0.5%; + float: left; } + +.small-screen-4, .medium-screen-4, .large-screen-4 { + width: 99%; } + +.small-screen-3, .medium-screen-3, .large-screen-3 { + width: 74%; } + +.small-screen-2, .medium-screen-2, .large-screen-2 { + width: 49%; } + +.small-screen-1, .medium-screen-1, .large-screen-1 { + width: 24%; } + +/* media query */ +@media screen and (min-width: 1000px) { + .hide-on-large { + display: none; } + + .small-screen-1 { + width: auto; } + + .medium-screen-1 { + width: auto; } + + .large-screen-1 { + width: 24% !important; } + + .large-screen-2 { + width: 49% !important; } + + .large-screen-3 { + width: 74% !important; } + + .large-screen-4 { + width: 99% !important; } } +@media screen and (min-width: 761px) and (max-width: 999px) { + .hide-on-medium { + display: none; } + + .small-screen-1 { + width: auto; } + + .medium-screen-1 { + width: 24% !important; } + + .medium-screen-2 { + width: 49% !important; } + + .medium-screen-3 { + width: 74% !important; } + + .medium-screen-4 { + width: 99% !important; } + + .large-screen-1 { + width: auto; } } +@media screen and (max-width: 760px) { + .hide-on-small { + display: none; } + + .small-screen-1 { + width: 24% !important; } + + .small-screen-2 { + width: 49% !important; } + + .small-screen-3 { + width: 74% !important; } + + .small-screen-4 { + width: 99% !important; } + + .medium-screen-1 { + width: auto; } + + .large-screen-1 { + width: auto; } } +/* colors */ +.black { + background: #101010; } + +.text-black { + color: #101010; } + +.light { + background: #f4f4f4; } + +.text-light { + color: #f4f4f4; } + +.grey { + background: #818181; } + +.text-grey { + color: #818181; } + +.white { + background: #ffffff; } + +.text-white { + color: #ffffff; } + +.primary { + background: #282667; } + +.text-primary { + color: #282667; } + +.secondary { + background: #1843b8; } + +.text-secondary { + color: #1843b8; } + +.danger { + background: #f53d3d; } + +.text-danger { + color: #f53d3d; } + +.success { + background: #1c8a4a; } + +.text-success { + color: #1c8a4a; } + +.info { + background: #1cbdee; } + +.text-info { + color: #1cbdee; } + +/* Padding Formatting */ +.xxlarge-padding { + padding: 60px; } + +.xlarge-padding { + padding: 30px; } + +.large-padding { + padding: 15px; } + +.medium-padding { + padding: 10px; } + +.small-padding { + padding: 5px; } + +.xxlarge-h-padding { + padding-left: 30px; + padding-right: 30px; } + +.xlarge-h-padding { + padding-left: 20px; + padding-right: 20px; } + +.large-h-padding { + padding-left: 15px; + padding-right: 15px; } + +.medium-h-padding { + padding-left: 10px; + padding-right: 10px; } + +.small-h-padding { + padding-left: 5px; + padding-right: 5px; } + +.xxlarge-v-padding { + padding-top: 30px; + padding-bottom: 30px; } + +.xlarge-v-padding { + padding-top: 20px; + padding-bottom: 20px; } + +.large-v-padding { + padding-top: 15px; + padding-bottom: 15px; } + +.medium-v-padding { + padding-top: 10px; + padding-bottom: 10px; } + +.small-v-padding { + padding-top: 5px; + padding-bottom: 5px; } + +/* shadow */ +.shadow-1 { + box-shadow: 0 0 5px rgba(0, 0, 0, 0.2); } + +.shadow-2 { + box-shadow: 0 0 10px rgba(0, 0, 0, 0.2); } + +.shadow-3 { + box-shadow: 0 0 12px rgba(0, 0, 0, 0.2); } + +.shadow-4 { + box-shadow: 0 0 16px rgba(0, 0, 0, 0.2); } + +.shadow-5 { + box-shadow: 0 0 24px rgba(0, 0, 0, 0.5); } + +.radius-1 { + border-radius: 5px; } + +.radius-2 { + border-radius: 10px; } + +.radius-3 { + border-radius: 15px; } + +.radius-4 { + border-radius: 20px; } + +.radius-5 { + border-radius: 30px; } + +.radius-6 { + border-radius: 50%; } + +body { + margin: 0; + font-family: "Open Sans", sans-serif; + margin-top: 80px; } + +a { + color: #101010; + text-decoration: none !important; } + +button:hover, a:hover { + opacity: 0.7; } + +article, aside, footer, header, nav, section { + display: block; + padding: 0.001em 0; } + +header { + padding: 5px 0; + width: 100%; + z-index: 150; } + +.fixheader { + top: 0; + position: fixed; } + +.logo { + margin: 5.3333333333px; + width: 180px; + height: 64px; } + +.logo img { + width: 180px; + height: 64px; } + +.right { + float: right; } + +.left { + float: left; } + +.center { + margin: 0 auto; } + +.left-align { + text-align: left; } + +.center-align { + text-align: center; } + +.right-align { + text-align: right; } + +/* more style options */ +/* no padding */ +.no-padding { + padding: 0 !important; } + +/* no margin */ +.no-margin { + margin: 0 !important; } + +/* no border */ +.no-border { + border: none !important; } + +/* no radius */ +.no-radius { + border-radius: none !important; } + +/* overflow hidden */ +.no-overflow { + overflow: hidden; } + +/* bold */ +.bold { + font-weight: bold; } + +.nobold { + font-weight: normal; } + +/* text transform */ +.capitilize { + text-transform: capitalize; } + +.uppercase { + text-transform: uppercase; } + +.no-hover { + background: none !important; } + +.divider { + width: 100%; + height: 1px; + margin: 12px 0; + clear: both; } + +.image { + width: 100%; + height: auto; } + +.image img { + width: 100%; + height: auto; } + +.card { + margin: 15px auto; + padding: 1%; + width: 98%; + display: block; } + +.card-info span { + padding: 5px; } + +.list-inline li { + display: inline-block; + float: none; } + +.list-block li { + display: block; } + +.tagsmenu { + padding: 7.5px 0; } + +.tagsmenu li { + padding: auto 0.4em; } + +.tagsmenu a { + padding: 0.5em; + font-size: 1em; } + +/*# sourceMappingURL=style.css.map */ diff --git a/templates/css/style.css.map b/templates/css/style.css.map new file mode 100644 index 00000000..7b41827e --- /dev/null +++ b/templates/css/style.css.map @@ -0,0 +1,7 @@ +{ +"version": 3, +"mappings": "AASA,aAAa;EAAC,SAAS,EAJb,IAAI;;AAKd,YAAY;EAAC,SAAS,EANb,IAAI;;AAOb,WAAW;EAAC,SAAS,EARb,IAAI;;AASZ,YAAY;EAAC,SAAS,EAVb,IAAI;;AAWb,WAAW;EAAC,SAAS,EAZb,IAAI;;ACDZ,kBAAc;EAAC,MAAM,EAAC,KAAc;;ACEpC,SAAS;AACT,IAAK;EAAC,WAAW,EAAC,IAAI;EAAC,YAAY,EAAC,IAAI;;AACxC,eAAe;AACf,UAAW;EAAC,MAAM,EAAE,MAAM;EAAC,SAAS,EAAE,MAAM;EAAC,KAAK,EAAC,GAAiB;;AAEpE,UAAU;AACV,8MAAmM;EAAC,MAAM,EAAE,MAAM;EAAC,KAAK,EAAC,IAAI;;AAC7N,kDAAgD;EAC5C,KAAK,EAAC,GACV;;AACA,kDAAgD;EAC5C,KAAK,EAAC,GACV;;AACA,kDAAgD;EAC5C,KAAK,EAAC,GACV;;AACA,kDAAgD;EAC5C,KAAK,EAAC,GACV;;AAGA,iBAAiB;AACjB,qCAAuC;EACnC,cAAc;IAAC,OAAO,EAAE,IAAI;;EAC5B,eAAe;IACX,KAAK,EAAC,IAAI;;EAEd,gBAAgB;IACZ,KAAK,EAAC,IAAI;;EAEd,eAAe;IACX,KAAK,EAAC,cACV;;EACA,eAAe;IACX,KAAK,EAAC,cACV;;EACA,eAAe;IACX,KAAK,EAAC,cACV;;EACA,eAAe;IACX,KAAK,EAAC,cACV;AAEJ,2DAA2D;EACvD,eAAe;IAAC,OAAO,EAAE,IAAI;;EAC7B,eAAe;IACX,KAAK,EAAC,IAAI;;EAEd,gBAAgB;IACZ,KAAK,EAAC,cACV;;EACA,gBAAgB;IACZ,KAAK,EAAC,cACV;;EACA,gBAAgB;IACZ,KAAK,EAAC,cACV;;EACA,gBAAgB;IACZ,KAAK,EAAC,cACV;;EACA,eAAe;IACX,KAAK,EAAC,IAAI;AAGlB,oCAAoC;EAChC,cAAc;IAAC,OAAO,EAAE,IAAI;;EAC5B,eAAe;IACX,KAAK,EAAC,cACV;;EACA,eAAe;IACX,KAAK,EAAC,cACV;;EACA,eAAe;IACX,KAAK,EAAC,cACV;;EACA,eAAe;IACX,KAAK,EAAC,cACV;;EACA,gBAAgB;IACZ,KAAK,EAAC,IAAI;;EAEd,eAAe;IACX,KAAK,EAAC,IAAI;ACzElB,YAAY;AACZ,MAAM;EAAC,UAAU,EAXV,OAAO;;AAWY,WAAW;EAAC,KAAK,EAXpC,OAAO;;AAYd,MAAM;EAAC,UAAU,EAXV,OAAO;;AAWY,WAAW;EAAC,KAAK,EAXpC,OAAO;;AAYd,KAAK;EAAC,UAAU,EAXT,OAAO;;AAWU,UAAU;EAAC,KAAK,EAXjC,OAAO;;AAYd,MAAM;EAAC,UAAU,EAXV,OAAO;;AAWY,WAAW;EAAC,KAAK,EAXpC,OAAO;;AAYd,QAAQ;EAAC,UAAU,EAXV,OAAO;;AAWc,aAAa;EAAC,KAAK,EAXxC,OAAO;;AAYhB,UAAU;EAAC,UAAU,EAXV,OAAO;;AAWgB,eAAe;EAAC,KAAK,EAX5C,OAAO;;AAYlB,OAAO;EAAC,UAAU,EAXV,OAAO;;AAWa,YAAY;EAAC,KAAK,EAXtC,OAAO;;AAYf,QAAQ;EAAC,UAAU,EAXV,OAAO;;AAWc,aAAa;EAAC,KAAK,EAXxC,OAAO;;AAYhB,KAAK;EAAC,UAAU,EAXV,OAAO;;AAWW,UAAU;EAAC,KAAK,EAXlC,OAAO;;ACRb,wBAAwB;AACxB,gBAAgB;EAAC,OAAO,EAAC,IAAc;;AACvC,eAAe;EAAC,OAAO,EAAC,IAAa;;AACrC,cAAc;EAAC,OAAO,EAAC,IAAa;;AACpC,eAAe;EAAC,OAAO,EAAC,IAAa;;AACrC,cAAc;EAAC,OAAO,EJNb,GAAG;;AISZ,kBAAkB;EAAC,YAAY,EAAC,IAAa;EAAC,aAAa,EAAC,IAAa;;AACzE,iBAAiB;EAAC,YAAY,EAAC,IAAa;EAAC,aAAa,EAAC,IAAa;;AACxE,gBAAgB;EAAC,YAAY,EAAC,IAAa;EAAC,aAAa,EAAC,IAAa;;AACvE,iBAAiB;EAAC,YAAY,EAAC,IAAa;EAAC,aAAa,EAAC,IAAa;;AACxE,gBAAgB;EAAC,YAAY,EJbpB,GAAG;EIa0B,aAAa,EJb1C,GAAG;;AIgBZ,kBAAkB;EAAC,WAAW,EAAC,IAAa;EAAC,cAAc,EAAC,IAAa;;AACzE,iBAAiB;EAAC,WAAW,EAAC,IAAa;EAAC,cAAc,EAAC,IAAa;;AACxE,gBAAgB;EAAC,WAAW,EAAC,IAAa;EAAC,cAAc,EAAC,IAAa;;AACvE,iBAAiB;EAAC,WAAW,EAAC,IAAa;EAAC,cAAc,EAAC,IAAa;;AACxE,gBAAgB;EAAC,WAAW,EJpBnB,GAAG;EIoByB,cAAc,EJpB1C,GAAG;;AKCZ,YAAY;AAEZ,SAAS;EAAC,UAAU,EAAE,0BAA2B;;AACjD,SAAS;EAAC,UAAU,EAAE,2BAAiC;;AACvD,SAAS;EAAC,UAAU,EAAE,2BAA0B;;AAChD,SAAS;EAAC,UAAU,EAAE,2BAA2B;;AACjD,SAAS;EAAC,UAAU,EAAE,2BAA0B;;ACNhD,SAAS;EAAC,aAAa,ENDd,GAAG;;AMEZ,SAAS;EAAC,aAAa,EAAC,IAAc;;AACtC,SAAS;EAAC,aAAa,EAAE,IAAc;;AACvC,SAAS;EAAC,aAAa,EAAE,IAAc;;AACvC,SAAS;EAAC,aAAa,EAAE,IAAc;;AACvC,SAAS;EAAC,aAAa,EAAE,GAAG;;ACM5B,IAAI;EAAC,MAAM,EAAC,CAAC;EAAC,WAAW,EAHjB,uBAAuB;EAGC,UAAU,EAAC,IAAc;;AACzD,CAAC;EAAC,KAAK,EJZA,OAAO;EIYC,eAAe,EAAE,eAAc;;AAC9C,qBAAqB;EAAC,OAAO,EAAE,GAAG;;AAGlC,4CAA6C;EAC3C,OAAO,EAAE,KAAK;EACd,OAAO,EAAC,SAAS;;AAInB,MAAM;EAAC,OAAO,EAAC,KAAS;EAAC,KAAK,EAAC,IAAI;EAAC,OAAO,EAAE,GAAG;;AAChD,UAAU;EAAC,GAAG,EAAC,CAAC;EAAC,QAAQ,EAAC,KAAK;;AAG/B,KAAK;EAAC,MAAM,EAAC,cAAY;EAAC,KAAK,EAAC,KAAqB;EAAC,MAAM,EAAC,IAAa;;AAC1E,SAAS;EAAC,KAAK,EAAC,KAAqB;EAAC,MAAM,EAAC,IAAa;;AAG1D,MAAM;EAAC,KAAK,EAAE,KAAK;;AACnB,KAAK;EAAC,KAAK,EAAE,IAAI;;AACjB,OAAO;EAAC,MAAM,EAAC,MAAM;;AAGrB,WAAW;EAAC,UAAU,EAAE,IAAI;;AAC5B,aAAa;EAAC,UAAU,EAAE,MAAM;;AAChC,YAAY;EAAC,UAAU,EAAE,KAAK;;AAI9B,wBAAwB;AACxB,gBAAgB;AAChB,WAAW;EAAC,OAAO,EAAE,YAAW;;AAChC,eAAe;AACf,UAAU;EAAC,MAAM,EAAE,YAAW;;AAC9B,eAAe;AACf,UAAU;EAAC,MAAM,EAAC,eAAc;;AAChC,eAAe;AACf,UAAU;EAAC,aAAa,EAAC,eAAc;;AACvC,qBAAqB;AACrB,YAAY;EAAC,QAAQ,EAAE,MAAM;;AAC7B,UAAU;AACV,KAAK;EAAC,WAAW,EAAE,IAAI;;AACvB,OAAO;EAAC,WAAW,EAAC,MAAM;;AAC1B,oBAAoB;AACpB,WAAW;EAAC,cAAc,EAAC,UAAU;;AACrC,UAAU;EAAC,cAAc,EAAE,SAAS;;AACpC,SAAS;EAAC,UAAU,EAAC,eAAc;;AAEnC,QAAQ;EAAC,KAAK,EAAC,IAAI;EAAC,MAAM,EAAC,GAAG;EAAC,MAAM,EAAE,MAAa;EAAC,KAAK,EAAC,IAAI;;AAG/D,MAAM;EAAC,KAAK,ELhEC,IAAI;EKgEQ,MAAM,EAAC,IAAI;;AACpC,UAAU;EAAC,KAAK,ELjEH,IAAI;EKiEY,MAAM,EAAE,IAAI;;AAGzC,KAAK;EAAC,MAAM,EAAC,SAAkB;EAAC,OAAO,EAAC,EAAE;EAAC,KAAK,EAAC,GAAG;EAAC,OAAO,EAAC,KAAK;;AAClE,eAAe;EAAC,OAAO,EPrEd,GAAG;;AOwEZ,eAAe;EAAC,OAAO,EAAC,YAAY;EAAC,KAAK,EAAC,IAAI;;AAC/C,cAAc;EAAC,OAAO,EAAC,KAAK;;AAG5B,SAAS;EAAC,OAAO,EAAC,OAAiB;;AACnC,YAAY;EAAC,OAAO,EAAC,UAAU;;AAC/B,WAAW;EAAC,OAAO,EAAC,KAAK;EAAC,SAAS,EAAC,GAAG", +"sources": ["../sass/_pixels.scss","../sass/_heading.scss","../sass/_grid.scss","../sass/_colors.scss","../sass/_padding.scss","../sass/_shadow.scss","../sass/_radius.scss","../sass/style.scss"], +"names": [], +"file": "style.css" +} diff --git a/templates/html/admin.html b/templates/html/admin.html new file mode 100644 index 00000000..b7b64eb0 --- /dev/null +++ b/templates/html/admin.html @@ -0,0 +1,239 @@ + + + + + + + + + Admin + + + +
+
+

+ Admin +

+
+
+
+ +
+ +
+
+
+
+
+ + + + + + + + + Activate
+ + Deactivate
+ + None
+
+ +
+
+
+ Response +

+        
+
+
+ + + + diff --git a/templates/html/allAuthors.html b/templates/html/allAuthors.html new file mode 100644 index 00000000..fa8a07af --- /dev/null +++ b/templates/html/allAuthors.html @@ -0,0 +1,71 @@ + + + + + + + + + + All authors + + + +
+
+

+ All authors +

+
+
+ +
+ +
+
+
+
+
+ + +
+
+
+ Response +

+    
+
+ + + + + + \ No newline at end of file diff --git a/templates/html/articleLike.html b/templates/html/articleLike.html new file mode 100644 index 00000000..78bf7e0b --- /dev/null +++ b/templates/html/articleLike.html @@ -0,0 +1,93 @@ + + + + + + + + + + Like articles + + + +
+
+

+ Like articles +

+
+
+ +
+ + +
+
+
+
+
+ + +
+
+
+ Response +

+    
+
+ + + + + + \ No newline at end of file diff --git a/templates/html/articles.html b/templates/html/articles.html new file mode 100644 index 00000000..1a04ca7e --- /dev/null +++ b/templates/html/articles.html @@ -0,0 +1,124 @@ + + + + + + + + + + Articles + + + +
+
+

+ Articles +

+
+
+
+ +
+ +
+
+
+
+
+ + + + + +
+
+
+ Response +

+    
+
+ + + + + + \ No newline at end of file diff --git a/templates/html/bookmark_article.html b/templates/html/bookmark_article.html new file mode 100644 index 00000000..0afd2bc5 --- /dev/null +++ b/templates/html/bookmark_article.html @@ -0,0 +1,98 @@ + + + + + + + + + + Bookmark Article + + + +
+
+

+ Bookmark Article +

+
+
+
+ +
+ +
+
+
+ +
+
+
+ Response +

+      
+
+
+ + + + + \ No newline at end of file diff --git a/templates/html/chat.html b/templates/html/chat.html new file mode 100644 index 00000000..90c17a5c --- /dev/null +++ b/templates/html/chat.html @@ -0,0 +1,284 @@ + + + + + + + + + Chat + + + +
+
+
+

+ Chat +

+
+
+
+ +
+ +
+
+
+
+
+ +
+ +
+
+
+ Response +

+        
+
+
+
+
+
+
+ + + + diff --git a/templates/html/commentLike.html b/templates/html/commentLike.html new file mode 100644 index 00000000..745d7588 --- /dev/null +++ b/templates/html/commentLike.html @@ -0,0 +1,93 @@ + + + + + + + + + + Like Comments + + + +
+
+

+ Like comments +

+
+
+ +
+ + +
+
+
+
+
+ + +
+
+
+ Response +

+    
+
+ + + + + + \ No newline at end of file diff --git a/templates/html/comments.html b/templates/html/comments.html new file mode 100644 index 00000000..dffc6001 --- /dev/null +++ b/templates/html/comments.html @@ -0,0 +1,105 @@ + + + + + + + + + + Comments + + + +
+
+

+ Comments +

+
+
+ +
+ +
+
+
+
+
+ + + + +
+
+
+ Response +

+    
+
+ + + + + + \ No newline at end of file diff --git a/templates/html/editComment.html b/templates/html/editComment.html new file mode 100644 index 00000000..d1cdf221 --- /dev/null +++ b/templates/html/editComment.html @@ -0,0 +1,107 @@ + + + + + + + + + + Comment Edits + + + +
+
+

+ History of comments Edits +

+
+
+ +
+ +
+
+
+
+
+ + + + +
+
+
+ Response +

+    
+
+ + + + + + \ No newline at end of file diff --git a/templates/html/favorite_article.html b/templates/html/favorite_article.html new file mode 100644 index 00000000..740c93ef --- /dev/null +++ b/templates/html/favorite_article.html @@ -0,0 +1,98 @@ + + + + + + + + + + Favorite Article + + + +
+
+

+ Favorite Article +

+
+
+
+ +
+ +
+
+
+ +
+
+
+ Response +

+      
+
+
+ + + + + \ No newline at end of file diff --git a/templates/html/follow.html b/templates/html/follow.html new file mode 100644 index 00000000..2d24fc4d --- /dev/null +++ b/templates/html/follow.html @@ -0,0 +1,200 @@ + + + + + + + + + Follow + + + +
+
+

+ Follow +

+
+
+
+ +
+ +
+
+
+
+
+ + + Follow
+ + Unfollow
+ + Following
+ + Followers
+
+ +
+
+
+ Response +

+        
+
+
+ + + + diff --git a/templates/html/highlight.html b/templates/html/highlight.html new file mode 100644 index 00000000..d4f55142 --- /dev/null +++ b/templates/html/highlight.html @@ -0,0 +1,182 @@ + + + + + + + + + Highlight + + + +
+
+

+ Highlight +

+
+
+ +
+ +
+
+
+
+
+ + + + +
+ +
+
+
+ Response +

+        
+
+
+ + + + diff --git a/templates/html/index.html b/templates/html/index.html new file mode 100644 index 00000000..727865a5 --- /dev/null +++ b/templates/html/index.html @@ -0,0 +1,44 @@ + + + + + Mockups - Authors Haven + + + + +
+
+
+
+
+
+ MOCKUPS +
+
+
+
+
+
+ +
+ +
+
+
+ +
+
+
+
+ Authors +
+
+
+
+ + diff --git a/templates/html/login.html b/templates/html/login.html new file mode 100644 index 00000000..b5ed30b8 --- /dev/null +++ b/templates/html/login.html @@ -0,0 +1,85 @@ + + + + + + + + + + Login + + + +
+
+

+ Login +

+
+
+ +
+ +
+
+
+
+
+ + +
+ +
+
+
+ Response +

+      
+
+
+ + + + + \ No newline at end of file diff --git a/templates/html/notifications.html b/templates/html/notifications.html new file mode 100644 index 00000000..d8932d32 --- /dev/null +++ b/templates/html/notifications.html @@ -0,0 +1,218 @@ + + + + + + + + + + Notifications + + + +
+
+

+ Notifications +

+
+
+
+ +
+ +
+
+
+
+
+
+ + +
+
+ + Configuration + +
+ + In App notification + + Show
+
+ + Articles + + Publish
+ Comment
+ Like
+
+
+
+
+ + Email notification + + Show
+
+ + Articles + + Publish
+ Comment
+ Like
+
+
+
+
+ +
+
+
+ Response +

+      
+
+
+ + + + + \ No newline at end of file diff --git a/templates/html/readingStats.html b/templates/html/readingStats.html new file mode 100644 index 00000000..1887a87c --- /dev/null +++ b/templates/html/readingStats.html @@ -0,0 +1,122 @@ + + + + + + + + + Reading Stats + + + +
+
+

+ Reading Stats +

+
+
+ +
+ +
+
+
+
+
+ +
+ +
+
+
+ Response +

+        
+
+
+ + + + diff --git a/templates/html/reportArticle.html b/templates/html/reportArticle.html new file mode 100644 index 00000000..dbacd7c3 --- /dev/null +++ b/templates/html/reportArticle.html @@ -0,0 +1,105 @@ + + + + + + + + + + Report + + + +
+
+

+ Report +

+
+
+ +
+ +
+
+
+
+
+ + + + +
+
+
+ Response +

+    
+
+ + + + + + \ No newline at end of file diff --git a/templates/html/reset_password.html b/templates/html/reset_password.html new file mode 100644 index 00000000..15f3dda2 --- /dev/null +++ b/templates/html/reset_password.html @@ -0,0 +1,174 @@ + + + + + + + + + RESET PASSWORD + + + +
+
+

+ RESET PASSWORD +

+
+
+ +
+ +
+
+
+
+
+ + + + + +
+ +
+
+
+ Response +

+        
+
+
+ + + + diff --git a/templates/html/signup.html b/templates/html/signup.html new file mode 100644 index 00000000..29ffc73b --- /dev/null +++ b/templates/html/signup.html @@ -0,0 +1,91 @@ + + + + + + + + + + Signup + + + +
+
+

+ Signup +

+
+
+ +
+ +
+
+
+
+
+ + + + + +
+ +
+
+
+ Response +

+      
+
+
+ + + + + \ No newline at end of file diff --git a/templates/images/image.png b/templates/images/image.png new file mode 100644 index 00000000..203c2f0f Binary files /dev/null and b/templates/images/image.png differ diff --git a/templates/images/mockup_all_articles.png b/templates/images/mockup_all_articles.png new file mode 100644 index 00000000..09e94e97 Binary files /dev/null and b/templates/images/mockup_all_articles.png differ diff --git a/templates/images/mockup_article.png b/templates/images/mockup_article.png new file mode 100644 index 00000000..0a8ac395 Binary files /dev/null and b/templates/images/mockup_article.png differ diff --git a/templates/images/mockup_get_article.png b/templates/images/mockup_get_article.png new file mode 100644 index 00000000..09e94e97 Binary files /dev/null and b/templates/images/mockup_get_article.png differ diff --git a/templates/images/mockup_login.png b/templates/images/mockup_login.png new file mode 100644 index 00000000..d87f385a Binary files /dev/null and b/templates/images/mockup_login.png differ diff --git a/templates/images/mockup_placeholder.jpg b/templates/images/mockup_placeholder.jpg new file mode 100644 index 00000000..2fa08a60 Binary files /dev/null and b/templates/images/mockup_placeholder.jpg differ diff --git a/templates/images/mockup_register.png b/templates/images/mockup_register.png new file mode 100644 index 00000000..7b52cbd1 Binary files /dev/null and b/templates/images/mockup_register.png differ diff --git a/templates/images/ninja.png b/templates/images/ninja.png new file mode 100644 index 00000000..3511b214 Binary files /dev/null and b/templates/images/ninja.png differ diff --git a/templates/js/index.js b/templates/js/index.js new file mode 100644 index 00000000..8c1fc7b1 --- /dev/null +++ b/templates/js/index.js @@ -0,0 +1,81 @@ +/* eslint-disable no-undef */ +/* eslint-disable no-unused-vars */ +/* eslint-disable require-jsdoc */ +const HOST = `${window.location.protocol}//${window.location.hostname}:${window.location.port}`; + +async function getData(URL, resType = 'text', token = '') { + try { + const request = new Request(URL, { + method: 'GET', + mode: 'cors', + cache: 'reload', + headers: { + 'access-token': token + } + }); + + let result = ''; + const response = await fetch(request); + + if (resType === 'json') { + result = await response.json(); + return result; + } + result = await response.text(); + return result; + } catch (e) { + throw Error(e); + } +} + +async function sendData(METHOD, URL, data = {}, resType = 'text', token = '') { + try { + const request = new Request(URL, { + method: METHOD, + mode: 'cors', + cache: 'no-cache', + headers: { + 'Content-Type': 'application/json; charset=utf-8', + 'access-token': token + }, + body: JSON.stringify(data) + }); + + let result = ''; + const response = await fetch(request); + + if (resType === 'json') { + result = await response.json(); + return result; + } + result = await response.text(); + return result; + } catch (e) { + throw Error(e); + } +} + +function displayUser() { + const user = localStorage.user ? JSON.parse(localStorage.user) : null; + + if (user) { + document.querySelector('.loggedInUser').innerHTML = `(${user.firstName} ${user.lastName})`; + } else { + document.querySelector('.loggedInUser').innerHTML = ''; + } +} + +function startLoadingButton() { + if (document.querySelector('#submit')) { + document.querySelector('#submit').innerHTML = 'Loading...'; + } +} +function endLoadingButton() { + if (document.querySelector('#submit')) { + document.querySelector('#submit').innerHTML = 'Submit'; + } +} + +window.document.addEventListener('DOMContentLoaded', () => { + displayUser(); +}); diff --git a/templates/sass/_colors.scss b/templates/sass/_colors.scss new file mode 100644 index 00000000..cf0ff29d --- /dev/null +++ b/templates/sass/_colors.scss @@ -0,0 +1,21 @@ + +$black:#101010; +$light:#f4f4f4; +$grey: #818181; +$white:#ffffff; +$primary:#282667; +$secondary:#1843b8; +$danger:#f53d3d; +$success:#1c8a4a; +$info:#1cbdee; + +/* colors */ +.black{background:$black} .text-black{color:$black} +.light{background:$light} .text-light{color:$light} +.grey{background:$grey} .text-grey{color:$grey} +.white{background:$white} .text-white{color:$white} +.primary{background:$primary} .text-primary{color:$primary} +.secondary{background:$secondary} .text-secondary{color:$secondary} +.danger{background:$danger} .text-danger{color:$danger} +.success{background:$success} .text-success{color:$success} +.info{background:$info} .text-info{color:$info} diff --git a/templates/sass/_grid.scss b/templates/sass/_grid.scss new file mode 100644 index 00000000..342aa03f --- /dev/null +++ b/templates/sass/_grid.scss @@ -0,0 +1,87 @@ +$full-width: 100%; + +/* row */ +.row {margin-left:0rem;margin-right:0rem;} +/* container */ +.container {margin: 0 auto;max-width: 1100px;width:($full-width - 2)} + +/* grid */ +.small-screen-1,.medium-screen-1,.large-screen-1,.small-screen-2,.medium-screen-2,.large-screen-2,.small-screen-3,.medium-screen-3,.large-screen-3,.small-screen-4,.medium-screen-4,.large-screen-4{margin: 0 0.5%;float:left} +.small-screen-4,.medium-screen-4,.large-screen-4{ + width:($full-width - 1) +} +.small-screen-3,.medium-screen-3,.large-screen-3{ + width:($full-width - 26) // 74% +} +.small-screen-2,.medium-screen-2,.large-screen-2{ + width:($full-width - 51) // 49% +} +.small-screen-1,.medium-screen-1,.large-screen-1{ + width:($full-width - 76) // 24 % +} + +// responsiveness +/* media query */ +@media screen and (min-width: 1000px) { + .hide-on-large{display: none} + .small-screen-1{ + width:auto + } + .medium-screen-1{ + width:auto + } + .large-screen-1{ + width:($full-width - 76)!important // 24% + } + .large-screen-2{ + width:($full-width - 51)!important // 49% + } + .large-screen-3{ + width:($full-width - 26)!important // 74% + } + .large-screen-4{ + width:($full-width - 1)!important // 99% + } +} +@media screen and (min-width: 761px) and (max-width: 999px){ + .hide-on-medium{display: none} + .small-screen-1{ + width:auto + } + .medium-screen-1{ + width:($full-width - 76)!important // 24% + } + .medium-screen-2{ + width:($full-width - 51)!important // 49% + } + .medium-screen-3{ + width:($full-width - 26)!important // 74% + } + .medium-screen-4{ + width:($full-width - 1)!important // 99% + } + .large-screen-1{ + width:auto + } +} +@media screen and (max-width: 760px){ + .hide-on-small{display: none} + .small-screen-1{ + width:($full-width - 76)!important // 24% + } + .small-screen-2{ + width:($full-width - 51)!important // 49% + } + .small-screen-3{ + width:($full-width - 26)!important // 74% + } + .small-screen-4{ + width:($full-width - 1)!important // 99% + } + .medium-screen-1{ + width:auto + } + .large-screen-1{ + width:auto + } +} \ No newline at end of file diff --git a/templates/sass/_heading.scss b/templates/sass/_heading.scss new file mode 100644 index 00000000..4aab4d76 --- /dev/null +++ b/templates/sass/_heading.scss @@ -0,0 +1 @@ +h1,h2,h3,h4,h5{margin:($small / 2) 0} \ No newline at end of file diff --git a/templates/sass/_loginpage.scss b/templates/sass/_loginpage.scss new file mode 100644 index 00000000..6c7136e9 --- /dev/null +++ b/templates/sass/_loginpage.scss @@ -0,0 +1,3 @@ +.loginpage { + max-width: 650px; +} diff --git a/templates/sass/_margin.scss b/templates/sass/_margin.scss new file mode 100644 index 00000000..e69de29b diff --git a/templates/sass/_padding.scss b/templates/sass/_padding.scss new file mode 100644 index 00000000..e34dc199 --- /dev/null +++ b/templates/sass/_padding.scss @@ -0,0 +1,21 @@ + +/* Padding Formatting */ +.xxlarge-padding{padding:($xsmall * 12)} +.xlarge-padding{padding:($xsmall * 6)} +.large-padding{padding:($xsmall * 3)} +.medium-padding{padding:($xsmall * 2)} +.small-padding{padding:$xsmall} + +// PADDING. Horizontal +.xxlarge-h-padding{padding-left:($xsmall * 6);padding-right:($xsmall * 6)} +.xlarge-h-padding{padding-left:($xsmall * 4);padding-right:($xsmall * 4)} +.large-h-padding{padding-left:($xsmall * 3);padding-right:($xsmall * 3)} +.medium-h-padding{padding-left:($xsmall * 2);padding-right:($xsmall * 2)} +.small-h-padding{padding-left:$xsmall;padding-right:$xsmall} + +// PADING. veritical +.xxlarge-v-padding{padding-top:($xsmall * 6);padding-bottom:($xsmall * 6)} +.xlarge-v-padding{padding-top:($xsmall * 4);padding-bottom:($xsmall * 4)} +.large-v-padding{padding-top:($xsmall * 3);padding-bottom:($xsmall * 3)} +.medium-v-padding{padding-top:($xsmall * 2);padding-bottom:($xsmall * 2)} +.small-v-padding{padding-top:$xsmall;padding-bottom:$xsmall} \ No newline at end of file diff --git a/templates/sass/_pixels.scss b/templates/sass/_pixels.scss new file mode 100644 index 00000000..fa2649fc --- /dev/null +++ b/templates/sass/_pixels.scss @@ -0,0 +1,14 @@ +$xsmall: 5px; +$small: 12px; +$medium: 16px; +$large: 24px; +$xlarge: 32px; +$xxlarge: 40px; + +// font text size + +.xxlarge-text{font-size: $xxlarge} +.xlarge-text{font-size: $xlarge} +.large-text{font-size: $large} +.medium-text{font-size: $medium} +.small-text{font-size:$small} \ No newline at end of file diff --git a/templates/sass/_radius.scss b/templates/sass/_radius.scss new file mode 100644 index 00000000..77b3bc00 --- /dev/null +++ b/templates/sass/_radius.scss @@ -0,0 +1,7 @@ +// rounds +.radius-1{border-radius: $xsmall } +.radius-2{border-radius:($xsmall * 2) } +.radius-3{border-radius: ($xsmall * 3) } +.radius-4{border-radius: ($xsmall * 4) } +.radius-5{border-radius: ($xsmall * 6) } +.radius-6{border-radius: 50%} \ No newline at end of file diff --git a/templates/sass/_shadow.scss b/templates/sass/_shadow.scss new file mode 100644 index 00000000..edaa89da --- /dev/null +++ b/templates/sass/_shadow.scss @@ -0,0 +1,8 @@ + +/* shadow */ + +.shadow-1{box-shadow: 0 0 $xsmall rgba(0,0,0,0.2)} +.shadow-2{box-shadow: 0 0 ($xsmall * 2) rgba(0,0,0,0.2)} +.shadow-3{box-shadow: 0 0 $small rgba(0,0,0,0.2)} +.shadow-4{box-shadow: 0 0 $medium rgba(0,0,0,0.2)} +.shadow-5{box-shadow: 0 0 $large rgba(0,0,0,0.5)} \ No newline at end of file diff --git a/templates/sass/style.scss b/templates/sass/style.scss new file mode 100644 index 00000000..48533499 --- /dev/null +++ b/templates/sass/style.scss @@ -0,0 +1,171 @@ +@import 'pixels'; +@import 'heading'; +@import 'grid'; +@import 'colors'; +@import 'margin'; +@import 'padding'; +@import 'shadow'; +@import 'radius'; +@import 'loginpage'; +// Font family +$font: 'Open Sans', sans-serif; + +// Styles +body { + margin: 0; + font-family: $font; + margin-top: ($xxlarge * 2); +} +a { + color: $black; + text-decoration: none !important; +} +button:hover, +a:hover { + opacity: 0.7; +} + +// html5 tags +article, +aside, +footer, +header, +nav, +section { + display: block; + padding: 0.001em 0; +} + +// fixed header +header { + padding: $xsmall 0; + width: 100%; + z-index: 150; +} +.fixheader { + top: 0; + position: fixed; +} + +// logo brand +.logo { + margin: ($medium / 3); + width: (($xxlarge * 4) + 20); + height: ($xlarge * 2); +} +.logo img { + width: (($xxlarge * 4) + 20); + height: ($xlarge * 2); +} + +// float +.right { + float: right; +} +.left { + float: left; +} +.center { + margin: 0 auto; +} + +// align +.left-align { + text-align: left; +} +.center-align { + text-align: center; +} +.right-align { + text-align: right; +} + +// helpers + +/* more style options */ +/* no padding */ +.no-padding { + padding: 0 !important; +} +/* no margin */ +.no-margin { + margin: 0 !important; +} +/* no border */ +.no-border { + border: none !important; +} +/* no radius */ +.no-radius { + border-radius: none !important; +} +/* overflow hidden */ +.no-overflow { + overflow: hidden; +} +/* bold */ +.bold { + font-weight: bold; +} +.nobold { + font-weight: normal; +} +/* text transform */ +.capitilize { + text-transform: capitalize; +} +.uppercase { + text-transform: uppercase; +} +.no-hover { + background: none !important; +} +// divider +.divider { + width: 100%; + height: 1px; + margin: ($large / 2) 0; + clear: both; +} + +// wrap images +.image { + width: $full-width; + height: auto; +} +.image img { + width: $full-width; + height: auto; +} + +// card +.card { + margin: ($xsmall * 3) auto; + padding: 1%; + width: 98%; + display: block; +} +.card-info span { + padding: $xsmall; +} + +// lists +.list-inline li { + display: inline-block; + float: none; +} +.list-block li { + display: block; +} + +// tags menu +.tagsmenu { + padding: ($xsmall * 1.5) 0; +} +.tagsmenu li { + padding: auto 0.4em; +} +.tagsmenu a { + padding: 0.5em; + font-size: 1em; +}