From 3a1e56cc8e7e654682a6409d5150e4eae72dc647 Mon Sep 17 00:00:00 2001 From: Matteo Pagliazzi Date: Fri, 23 Feb 2018 15:21:00 +0100 Subject: [PATCH 01/15] Upgrade server deps (#10017) * remove unused apn lib and upgrade moment-recur * upgrade validator * upgrade got * request -> got * fix validation * fix tests * upgrade nodemailer * fix unit tests * fix webhook tests, upgrade express-validator (using legacy api) * upgrade js2xmlparser * update misc packages * fix linting * update packages --- package-lock.json | 776 ++++++++++++------ package.json | 46 +- .../api/v3/integration/chat/POST-chat.test.js | 8 +- .../integration/tasks/GET-tasks_user.test.js | 4 +- .../POST-tasks_clearCompletedTodos.test.js | 4 +- .../webhook/PUT-user_update_webhook.test.js | 2 +- test/api/v3/unit/libs/email.test.js | 39 +- test/api/v3/unit/libs/webhooks.test.js | 61 +- test/helpers/api-unit.helper.js | 16 + website/client/store/actions/user.js | 4 +- website/server/controllers/api-v3/auth.js | 4 +- .../controllers/top-level/dataexport.js | 7 +- website/server/libs/applePayments.js | 6 +- website/server/libs/email.js | 19 +- website/server/libs/pushNotifications.js | 17 +- website/server/libs/webhook.js | 18 +- website/server/models/task.js | 2 +- website/server/models/webhook.js | 10 +- 18 files changed, 646 insertions(+), 397 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4ae851123f..569840dc8a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,60 +5,88 @@ "requires": true, "dependencies": { "@babel/code-frame": { - "version": "7.0.0-beta.36", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0-beta.36.tgz", - "integrity": "sha512-sW77BFwJ48YvQp3Gzz5xtAUiXuYOL2aMJKDwiaY3OcvdqBFurtYfOpSa4QrNyDxmOGRFSYzUpabU2m9QrlWE7w==", + "version": "7.0.0-beta.40", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0-beta.40.tgz", + "integrity": "sha512-eVXQSbu/RimU6OKcK2/gDJVTFcxXJI4sHbIqw2mhwMZeQ2as/8AhS9DGkEDoHMBBNJZ5B0US63lF56x+KDcxiA==", "requires": { - "chalk": "2.3.0", + "@babel/highlight": "7.0.0-beta.40" + } + }, + "@babel/generator": { + "version": "7.0.0-beta.40", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.0.0-beta.40.tgz", + "integrity": "sha512-c91BQcXyTq/5aFV4afgOionxZS1dxWt8OghEx5Q52SKssdGRFSiMKnk9tGkev1pYULPJBqjSDZU2Pcuc58ffZw==", + "requires": { + "@babel/types": "7.0.0-beta.40", + "jsesc": "2.5.1", + "lodash": "4.17.5", + "source-map": "0.5.7", + "trim-right": "1.0.1" + }, + "dependencies": { + "jsesc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.1.tgz", + "integrity": "sha1-5CGiqOINawgZ3yiQj3glJrlt0f4=" + } + } + }, + "@babel/helper-function-name": { + "version": "7.0.0-beta.40", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.0.0-beta.40.tgz", + "integrity": "sha512-cK9BVLtOfisSISTTHXKGvBc2OBh65tjEk4PgXhsSnnH0i8RP2v+5RCxoSlh2y/i+l2fxQqKqv++Qo5RMiwmRCA==", + "requires": { + "@babel/helper-get-function-arity": "7.0.0-beta.40", + "@babel/template": "7.0.0-beta.40", + "@babel/types": "7.0.0-beta.40" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.0.0-beta.40", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0-beta.40.tgz", + "integrity": "sha512-MwquaPznI4cUoZEgHC/XGkddOXtqKqD4DvZDOyJK2LR9Qi6TbMbAhc6IaFoRX7CRTFCmtGeu8gdXW2dBotBBTA==", + "requires": { + "@babel/types": "7.0.0-beta.40" + } + }, + "@babel/highlight": { + "version": "7.0.0-beta.40", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0-beta.40.tgz", + "integrity": "sha512-mOhhTrzieV6VO7odgzFGFapiwRK0ei8RZRhfzHhb6cpX3QM8XXuCLXWjN8qBB7JReDdUR80V3LFfFrGUYevhNg==", + "requires": { + "chalk": "2.3.1", "esutils": "2.0.2", "js-tokens": "3.0.2" } }, - "@babel/helper-function-name": { - "version": "7.0.0-beta.36", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.0.0-beta.36.tgz", - "integrity": "sha512-/SGPOyifPf20iTrMN+WdlY2MbKa7/o4j7B/4IAsdOusASp2icT+Wcdjf4tjJHaXNX8Pe9bpgVxLNxhRvcf8E5w==", - "requires": { - "@babel/helper-get-function-arity": "7.0.0-beta.36", - "@babel/template": "7.0.0-beta.36", - "@babel/types": "7.0.0-beta.36" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.0.0-beta.36", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0-beta.36.tgz", - "integrity": "sha512-vPPcx2vsSoDbcyWr9S3nd0FM3B4hEXnt0p1oKpwa08GwK0fSRxa98MyaRGf8suk8frdQlG1P3mDrz5p/Rr3pbA==", - "requires": { - "@babel/types": "7.0.0-beta.36" - } - }, "@babel/template": { - "version": "7.0.0-beta.36", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.0.0-beta.36.tgz", - "integrity": "sha512-mUBi90WRyZ9iVvlWLEdeo8gn/tROyJdjKNC4W5xJTSZL+9MS89rTJSqiaJKXIkxk/YRDL/g/8snrG/O0xl33uA==", + "version": "7.0.0-beta.40", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.0.0-beta.40.tgz", + "integrity": "sha512-RlQiVB7eL7fxsKN6JvnCCwEwEL28CBYalXSgWWULuFlEHjtMoXBqQanSie3bNyhrANJx67sb+Sd/vuGivoMwLQ==", "requires": { - "@babel/code-frame": "7.0.0-beta.36", - "@babel/types": "7.0.0-beta.36", - "babylon": "7.0.0-beta.36", + "@babel/code-frame": "7.0.0-beta.40", + "@babel/types": "7.0.0-beta.40", + "babylon": "7.0.0-beta.40", "lodash": "4.17.5" }, "dependencies": { "babylon": { - "version": "7.0.0-beta.36", - "resolved": "https://registry.npmjs.org/babylon/-/babylon-7.0.0-beta.36.tgz", - "integrity": "sha512-rw4YdadGwajAMMRl6a5swhQ0JCOOFyaYCfJ0AsmNBD8uBD/r4J8mux7wBaqavvFKqUKQYWOzA1Speams4YDzsQ==" + "version": "7.0.0-beta.40", + "resolved": "https://registry.npmjs.org/babylon/-/babylon-7.0.0-beta.40.tgz", + "integrity": "sha512-AVxF2EcxvGD5hhOuLTOLAXBb0VhwWpEX0HyHdAI2zU+AAP4qEwtQj8voz1JR3uclGai0rfcE+dCTHnNMOnimFg==" } } }, "@babel/traverse": { - "version": "7.0.0-beta.36", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.0.0-beta.36.tgz", - "integrity": "sha512-OTUb6iSKVR/98dGThRJ1BiyfwbuX10BVnkz89IpaerjTPRhDfMBfLsqmzxz5MiywUOW4M0Clta0o7rSxkfcuzw==", + "version": "7.0.0-beta.40", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.0.0-beta.40.tgz", + "integrity": "sha512-h96SQorjvdSuxQ6hHFIuAa3oxnad1TA5bU1Zz88+XqzwmM5QM0/k2D+heXGGy/76gT5ajl7xYLKGiPA/KTyVhQ==", "requires": { - "@babel/code-frame": "7.0.0-beta.36", - "@babel/helper-function-name": "7.0.0-beta.36", - "@babel/types": "7.0.0-beta.36", - "babylon": "7.0.0-beta.36", + "@babel/code-frame": "7.0.0-beta.40", + "@babel/generator": "7.0.0-beta.40", + "@babel/helper-function-name": "7.0.0-beta.40", + "@babel/types": "7.0.0-beta.40", + "babylon": "7.0.0-beta.40", "debug": "3.1.0", "globals": "11.3.0", "invariant": "2.2.2", @@ -66,9 +94,9 @@ }, "dependencies": { "babylon": { - "version": "7.0.0-beta.36", - "resolved": "https://registry.npmjs.org/babylon/-/babylon-7.0.0-beta.36.tgz", - "integrity": "sha512-rw4YdadGwajAMMRl6a5swhQ0JCOOFyaYCfJ0AsmNBD8uBD/r4J8mux7wBaqavvFKqUKQYWOzA1Speams4YDzsQ==" + "version": "7.0.0-beta.40", + "resolved": "https://registry.npmjs.org/babylon/-/babylon-7.0.0-beta.40.tgz", + "integrity": "sha512-AVxF2EcxvGD5hhOuLTOLAXBb0VhwWpEX0HyHdAI2zU+AAP4qEwtQj8voz1JR3uclGai0rfcE+dCTHnNMOnimFg==" }, "debug": { "version": "3.1.0", @@ -86,9 +114,9 @@ } }, "@babel/types": { - "version": "7.0.0-beta.36", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.0.0-beta.36.tgz", - "integrity": "sha512-PyAORDO9um9tfnrddXgmWN9e6Sq9qxraQIt5ynqBOSXKA5qvK1kUr+Q3nSzKFdzorsiK+oqcUnAFvEoKxv9D+Q==", + "version": "7.0.0-beta.40", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.0.0-beta.40.tgz", + "integrity": "sha512-uXCGCzTgMZxcSUzutCPtZmXbVC+cvENgS2e0tRuhn+Y1hZnMb8IHP0Trq7Q2MB/eFmG5pKrAeTIUfQIe5kA4Tg==", "requires": { "esutils": "2.0.2", "lodash": "4.17.5", @@ -102,6 +130,28 @@ } } }, + "@sindresorhus/is": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.7.0.tgz", + "integrity": "sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow==" + }, + "@sinonjs/formatio": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-2.0.0.tgz", + "integrity": "sha512-ls6CAMA6/5gG+O/IdsBcblvnd8qcO/l1TYoNeAzp3wcISOxlPXQEus0mLcdwazEkWjaBdaJ3TaxmNgCLWwvWzg==", + "dev": true, + "requires": { + "samsam": "1.3.0" + }, + "dependencies": { + "samsam": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/samsam/-/samsam-1.3.0.tgz", + "integrity": "sha512-1HwIYD/8UlOtFS3QO3w7ey+SdSDFE4HRNLZoZRYVQefrOY3l17epswImeB1ijgJFQJodIaHcwkp3r/myBjFVbg==", + "dev": true + } + } + }, "@slack/client": { "version": "3.16.0", "resolved": "https://registry.npmjs.org/@slack/client/-/client-3.16.0.tgz", @@ -234,7 +284,9 @@ "addressparser": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/addressparser/-/addressparser-1.0.1.tgz", - "integrity": "sha1-R6++GiqSYhkdtoOOT9HTm0CCF0Y=" + "integrity": "sha1-R6++GiqSYhkdtoOOT9HTm0CCF0Y=", + "dev": true, + "optional": true }, "after": { "version": "0.8.2", @@ -950,9 +1002,9 @@ } }, "aws-sdk": { - "version": "2.189.0", - "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.189.0.tgz", - "integrity": "sha1-TfZJuM+iAI/uCfNX/jqYNUZcTaQ=", + "version": "2.200.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.200.0.tgz", + "integrity": "sha1-9GDJZAhyWw64xlj93qbgv+DvWkQ=", "requires": { "buffer": "4.9.1", "events": "1.1.1", @@ -1005,9 +1057,9 @@ "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=" }, "axios": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.17.1.tgz", - "integrity": "sha1-LY4+XQvb1zJ/kbyBT1xXZg+Bgk0=", + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.18.0.tgz", + "integrity": "sha1-MtU+SFHv3AoRmTts0AB4nXDAUQI=", "requires": { "follow-redirects": "1.4.1", "is-buffer": "1.1.6" @@ -1069,22 +1121,22 @@ } }, "babel-eslint": { - "version": "8.2.1", - "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-8.2.1.tgz", - "integrity": "sha512-RzdVOyWKQRUnLXhwLk+eKb4oyW+BykZSkpYwFhM4tnfzAG5OWfvG0w/uyzMp5XKEU0jN82+JefHr39bG2+KhRQ==", + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-8.2.2.tgz", + "integrity": "sha512-Qt2lz2egBxNYWqN9JIO2z4NOOf8i4b5JS6CFoYrOZZTDssueiV1jH/jsefyg+86SeNY3rB361/mi3kE1WK2WYQ==", "requires": { - "@babel/code-frame": "7.0.0-beta.36", - "@babel/traverse": "7.0.0-beta.36", - "@babel/types": "7.0.0-beta.36", - "babylon": "7.0.0-beta.36", + "@babel/code-frame": "7.0.0-beta.40", + "@babel/traverse": "7.0.0-beta.40", + "@babel/types": "7.0.0-beta.40", + "babylon": "7.0.0-beta.40", "eslint-scope": "3.7.1", "eslint-visitor-keys": "1.0.0" }, "dependencies": { "babylon": { - "version": "7.0.0-beta.36", - "resolved": "https://registry.npmjs.org/babylon/-/babylon-7.0.0-beta.36.tgz", - "integrity": "sha512-rw4YdadGwajAMMRl6a5swhQ0JCOOFyaYCfJ0AsmNBD8uBD/r4J8mux7wBaqavvFKqUKQYWOzA1Speams4YDzsQ==" + "version": "7.0.0-beta.40", + "resolved": "https://registry.npmjs.org/babylon/-/babylon-7.0.0-beta.40.tgz", + "integrity": "sha512-AVxF2EcxvGD5hhOuLTOLAXBb0VhwWpEX0HyHdAI2zU+AAP4qEwtQj8voz1JR3uclGai0rfcE+dCTHnNMOnimFg==" } } }, @@ -2439,6 +2491,8 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/buildmail/-/buildmail-4.0.1.tgz", "integrity": "sha1-h393OLeHKYccmhBeO4N9K+EaenI=", + "dev": true, + "optional": true, "requires": { "addressparser": "1.0.1", "libbase64": "0.1.0", @@ -2485,6 +2539,55 @@ "unset-value": "1.0.0" } }, + "cacheable-request": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-2.1.4.tgz", + "integrity": "sha1-DYCIAbY0KtM8kd+dC0TcCbkeXD0=", + "requires": { + "clone-response": "1.0.2", + "get-stream": "3.0.0", + "http-cache-semantics": "3.8.1", + "keyv": "3.0.0", + "lowercase-keys": "1.0.0", + "normalize-url": "2.0.1", + "responselike": "1.0.2" + }, + "dependencies": { + "normalize-url": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-2.0.1.tgz", + "integrity": "sha512-D6MUW4K/VzoJ4rJ01JFKxDrtY1v9wrgzCX5f2qj/lzH1m/lW6MhUZFKerVsnyjOhOsYzI9Kqqak+10l4LvLpMw==", + "requires": { + "prepend-http": "2.0.0", + "query-string": "5.1.0", + "sort-keys": "2.0.0" + } + }, + "prepend-http": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", + "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=" + }, + "query-string": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-5.1.0.tgz", + "integrity": "sha512-F3DkxxlY0AqD/rwe4YAwjRE2HjOkKW7TxsuteyrS/Jbwrxw887PqYBL4sWUJ9D/V1hmFns0SCD6FDyvlwo9RCQ==", + "requires": { + "decode-uri-component": "0.2.0", + "object-assign": "4.1.1", + "strict-uri-encode": "1.1.0" + } + }, + "sort-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-2.0.0.tgz", + "integrity": "sha1-ZYU1WEhh7JfXMNbPQYIuH1ZoQSg=", + "requires": { + "is-plain-obj": "1.1.0" + } + } + } + }, "cached-constructors-x": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/cached-constructors-x/-/cached-constructors-x-1.0.0.tgz", @@ -2688,13 +2791,13 @@ "dev": true }, "chalk": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz", - "integrity": "sha512-Az5zJR2CBujap2rqXGaJKaPHyJ0IrUimvYNX+ncCy8PJP4ltOGTrHUIo097ZaL2zMeKYpiCdqDvS6zdrTFok3Q==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.1.tgz", + "integrity": "sha512-QUU4ofkDoMIVO7hcx1iPTISs88wsO8jA92RQIm4JAwZvFGGAV2hSAA1NX7oVj2Ej2Q6NDTcRDjPTFrMCRZoJ6g==", "requires": { "ansi-styles": "3.2.0", "escape-string-regexp": "1.0.5", - "supports-color": "4.5.0" + "supports-color": "5.2.0" }, "dependencies": { "ansi-styles": { @@ -2706,16 +2809,16 @@ } }, "has-flag": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", - "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" }, "supports-color": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", - "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.2.0.tgz", + "integrity": "sha512-F39vS48la4YvTZUPVeTqsjsFNrvcMwrV3RLZINsmHo+7djCvuUzSIeXOnZ5hmjef4bajL1dNccN+tg5XAliO5Q==", "requires": { - "has-flag": "2.0.0" + "has-flag": "3.0.0" } } } @@ -3057,6 +3160,14 @@ } } }, + "clone-response": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", + "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", + "requires": { + "mimic-response": "1.0.0" + } + }, "clone-stats": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", @@ -3237,21 +3348,28 @@ "dev": true }, "compressible": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.12.tgz", - "integrity": "sha1-xZpcmdt2dn6YdlAOJx72OzSTvWY=", + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.13.tgz", + "integrity": "sha1-DRAgq5JLL9tNYnmHXH1tq6a6p6k=", "requires": { - "mime-db": "1.30.0" + "mime-db": "1.33.0" + }, + "dependencies": { + "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==" + } } }, "compression": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.1.tgz", - "integrity": "sha1-7/JgPvwuIs+G810uuTWJ+YdTc9s=", + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.2.tgz", + "integrity": "sha1-qv+81qr4VLROuygDU9WtFlH1mmk=", "requires": { "accepts": "1.3.4", "bytes": "3.0.0", - "compressible": "2.0.12", + "compressible": "2.0.13", "debug": "2.6.9", "on-headers": "1.0.1", "safe-buffer": "5.1.1", @@ -3813,9 +3931,9 @@ } }, "csv-stringify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/csv-stringify/-/csv-stringify-2.0.1.tgz", - "integrity": "sha512-qoWgXJHmANfwIZFogezZjfN7KeaALrSe2zA5velb2tVEx0r7tgVuMfideB5gq12x5Z7LkvgOayXplOwlKcXnKQ==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/csv-stringify/-/csv-stringify-2.0.4.tgz", + "integrity": "sha512-rm8JxNYXglaDKExA2KqCKY+jx8qVBSkXm8/bgw2BIyzLq1/qni/QMSPBchAkd8RGsJhMJDQ+ZxikG/Qgvck+gQ==", "requires": { "lodash.get": "4.4.2" } @@ -4172,6 +4290,14 @@ } } }, + "decompress-response": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", + "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", + "requires": { + "mimic-response": "1.0.0" + } + }, "decompress-tar": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/decompress-tar/-/decompress-tar-3.1.0.tgz", @@ -5484,14 +5610,14 @@ } }, "eslint": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-4.18.0.tgz", - "integrity": "sha512-Ep2lUbztzXLg0gNUl48I1xvbQFy1QuWyh1C9PSympmln33jwOr8B3QfuEcXpPPE4uSwEzDaWhUxBN0sNQkzrBg==", + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-4.18.1.tgz", + "integrity": "sha512-gPSfpSRCHre1GLxGmO68tZNxOlL2y7xBd95VcLD+Eo4S2js31YoMum3CAQIOaxY24hqYOMksMvW38xuuWKQTgw==", "dev": true, "requires": { "ajv": "5.5.2", "babel-code-frame": "6.26.0", - "chalk": "2.3.0", + "chalk": "2.3.1", "concat-stream": "1.6.0", "cross-spawn": "5.1.0", "debug": "3.1.0", @@ -6171,38 +6297,20 @@ } }, "express-basic-auth": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/express-basic-auth/-/express-basic-auth-1.1.3.tgz", - "integrity": "sha512-+tYtERHfl46Y1sPexskNcnIgYCmEnvj4Hp8/19dfByMUQAAWKEQ/Ik1taQ+Kj9LLJP1ItuXa95Ta8e8LtyltVQ==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/express-basic-auth/-/express-basic-auth-1.1.4.tgz", + "integrity": "sha512-stNYRMPULJu/Tk3aFSdkyFMHDPDukens2L5FrTHJab+k/gvBdjB885W3gAZtHNrUaSmbAO8dQ9Omcot9qtbAag==", "requires": { "basic-auth": "1.1.0" } }, "express-validator": { - "version": "2.21.0", - "resolved": "https://registry.npmjs.org/express-validator/-/express-validator-2.21.0.tgz", - "integrity": "sha1-9fwvn6npqFeGNPEOhrpaQ0a5b08=", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/express-validator/-/express-validator-5.0.1.tgz", + "integrity": "sha512-M5SgmiyzAzdqnaCAgqvQpiC7KpCBeIqcwYxivTv32ALWk+ZQG/E2DlxH5FoIOsO5m/V9xO8SGyghtz5Waxz35A==", "requires": { - "bluebird": "3.4.7", - "lodash": "4.16.6", - "validator": "5.7.0" - }, - "dependencies": { - "bluebird": { - "version": "3.4.7", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz", - "integrity": "sha1-9y12C+Cbf3bQjtj66Ysomo0F+rM=" - }, - "lodash": { - "version": "4.16.6", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.16.6.tgz", - "integrity": "sha1-0iyaxmAojzhD4Wun0rXQbMon13c=" - }, - "validator": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/validator/-/validator-5.7.0.tgz", - "integrity": "sha1-eoelgUa2laxIYHEUHAxJ1n2gXlw=" - } + "lodash": "4.17.5", + "validator": "9.4.1" } }, "extend": { @@ -6727,6 +6835,15 @@ "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", "integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=" }, + "from2": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", + "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", + "requires": { + "inherits": "2.0.3", + "readable-stream": "2.3.3" + } + }, "fs-access": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/fs-access/-/fs-access-1.0.1.tgz", @@ -8125,21 +8242,47 @@ } }, "got": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/got/-/got-6.7.1.tgz", - "integrity": "sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=", + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/got/-/got-8.2.0.tgz", + "integrity": "sha512-giadqJpXIwjY+ZsuWys8p2yjZGhOHiU4hiJHjS/oeCxw1u8vANQz3zPlrxW2Zw/siCXsSMI3hvzWGcnFyujyAg==", "requires": { - "create-error-class": "3.0.2", + "@sindresorhus/is": "0.7.0", + "cacheable-request": "2.1.4", + "decompress-response": "3.3.0", "duplexer3": "0.1.4", "get-stream": "3.0.0", - "is-redirect": "1.0.0", + "into-stream": "3.1.0", "is-retry-allowed": "1.1.0", - "is-stream": "1.1.0", + "isurl": "1.0.0", "lowercase-keys": "1.0.0", + "mimic-response": "1.0.0", + "p-cancelable": "0.3.0", + "p-timeout": "2.0.1", + "pify": "3.0.0", "safe-buffer": "5.1.1", "timed-out": "4.0.1", - "unzip-response": "2.0.1", - "url-parse-lax": "1.0.0" + "url-parse-lax": "3.0.0", + "url-to-options": "1.0.1" + }, + "dependencies": { + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" + }, + "prepend-http": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", + "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=" + }, + "url-parse-lax": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", + "integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=", + "requires": { + "prepend-http": "2.0.0" + } + } } }, "graceful-fs": { @@ -9108,6 +9251,11 @@ } } }, + "http-cache-semantics": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz", + "integrity": "sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w==" + }, "http-errors": { "version": "1.6.2", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", @@ -9283,6 +9431,7 @@ "version": "1.6.1", "resolved": "https://registry.npmjs.org/httpntlm/-/httpntlm-1.6.1.tgz", "integrity": "sha1-rQFScUOi6Hc8+uapb1hla7UqNLI=", + "dev": true, "requires": { "httpreq": "0.4.24", "underscore": "1.7.0" @@ -9291,14 +9440,16 @@ "underscore": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.7.0.tgz", - "integrity": "sha1-a7rwh3UA02vjTsqlhODbn+8DUgk=" + "integrity": "sha1-a7rwh3UA02vjTsqlhODbn+8DUgk=", + "dev": true } } }, "httpreq": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/httpreq/-/httpreq-0.4.24.tgz", - "integrity": "sha1-QzX/2CzZaWaKOUZckprGHWOTYn8=" + "integrity": "sha1-QzX/2CzZaWaKOUZckprGHWOTYn8=", + "dev": true }, "https-browserify": { "version": "1.0.0", @@ -9790,6 +9941,15 @@ "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.1.0.tgz", "integrity": "sha1-ftGxQQxqDg94z5XTuEQMY/eLhhQ=" }, + "into-stream": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-3.1.0.tgz", + "integrity": "sha1-lvsKk2wSur1v8XUqF9BWFqvQlMY=", + "requires": { + "from2": "2.3.0", + "p-is-promise": "1.1.0" + } + }, "intro.js": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/intro.js/-/intro.js-2.7.0.tgz", @@ -9816,7 +9976,8 @@ "ip": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", - "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=" + "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=", + "dev": true }, "ip-regex": { "version": "1.0.3", @@ -10131,6 +10292,11 @@ "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=" }, + "is-object": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-object/-/is-object-1.0.1.tgz", + "integrity": "sha1-iVJojF7C/9awPsyF52ngKQMINHA=" + }, "is-object-like-x": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/is-object-like-x/-/is-object-like-x-1.6.0.tgz", @@ -10497,6 +10663,15 @@ "handlebars": "4.0.11" } }, + "isurl": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isurl/-/isurl-1.0.0.tgz", + "integrity": "sha512-1P/yWsxPlDtn7QeRD+ULKQPaIaN6yF368GZ2vDfv0AL0NwpStafjWCDDdn0k8wgFMWpVAqG7oJhxHnlud42i9w==", + "requires": { + "has-to-string-tag-x": "1.4.1", + "is-object": "1.0.1" + } + }, "jmespath": { "version": "0.15.0", "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz", @@ -10548,9 +10723,12 @@ } }, "js2xmlparser": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-1.0.0.tgz", - "integrity": "sha1-WhcPLo1kds5FQF4EgjJCUTeC/jA=" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-3.0.0.tgz", + "integrity": "sha1-P7YOqgicVED5MZ9RdgzNB+JJlzM=", + "requires": { + "xmlcreate": "1.0.2" + } }, "jsbn": { "version": "0.1.1", @@ -10563,6 +10741,11 @@ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=" }, + "json-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", + "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=" + }, "json-content-demux": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/json-content-demux/-/json-content-demux-0.1.3.tgz", @@ -11271,6 +11454,14 @@ "resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.0.2.tgz", "integrity": "sha1-rTKXxVcGneqLz+ek+kkbdcXd65E=" }, + "keyv": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.0.0.tgz", + "integrity": "sha512-eguHnq22OE3uVoSYG0LVWNP+4ppamWr9+zWBe1bsNcovIMy6huUJFPgy4mGwCd/rnl3vOLGW1MTlu4c57CT1xA==", + "requires": { + "json-buffer": "3.0.0" + } + }, "kind-of": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", @@ -11644,12 +11835,14 @@ "libbase64": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/libbase64/-/libbase64-0.1.0.tgz", - "integrity": "sha1-YjUag5VjrF/1vSbxL2Dpgwu3UeY=" + "integrity": "sha1-YjUag5VjrF/1vSbxL2Dpgwu3UeY=", + "dev": true }, "libmime": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/libmime/-/libmime-3.0.0.tgz", "integrity": "sha1-UaGp50SOy9Ms2lRCFnW7IbwJPaY=", + "dev": true, "requires": { "iconv-lite": "0.4.15", "libbase64": "0.1.0", @@ -11659,14 +11852,16 @@ "iconv-lite": { "version": "0.4.15", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.15.tgz", - "integrity": "sha1-/iZaIYrGpXz+hUkn6dBMGYJe3es=" + "integrity": "sha1-/iZaIYrGpXz+hUkn6dBMGYJe3es=", + "dev": true } } }, "libqp": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/libqp/-/libqp-1.1.0.tgz", - "integrity": "sha1-9ebgatdLeU+1tbZpiL9yjvHe2+g=" + "integrity": "sha1-9ebgatdLeU+1tbZpiL9yjvHe2+g=", + "dev": true }, "liftoff": { "version": "2.5.0", @@ -12141,6 +12336,22 @@ } } }, + "nodemailer": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-2.7.2.tgz", + "integrity": "sha1-8kLmSa7q45tsftdA73sGHEBNMPk=", + "dev": true, + "optional": true, + "requires": { + "libmime": "3.0.0", + "mailcomposer": "4.0.1", + "nodemailer-direct-transport": "3.3.2", + "nodemailer-shared": "1.1.0", + "nodemailer-smtp-pool": "2.8.2", + "nodemailer-smtp-transport": "2.7.2", + "socks": "1.1.9" + } + }, "semver": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", @@ -12347,6 +12558,8 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/mailcomposer/-/mailcomposer-4.0.1.tgz", "integrity": "sha1-DhxEsqB890DuF9wUm6AJ8Zyt/rQ=", + "dev": true, + "optional": true, "requires": { "buildmail": "4.0.1", "libmime": "3.0.0" @@ -12687,6 +12900,11 @@ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==" }, + "mimic-response": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.0.tgz", + "integrity": "sha1-3z02Uqc/3ta5sLJBRub9BSNTRY4=" + }, "minimalistic-assert": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.0.tgz", @@ -12760,9 +12978,9 @@ "dev": true }, "mocha": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.0.0.tgz", - "integrity": "sha512-ukB2dF+u4aeJjc6IGtPNnJXfeby5d4ZqySlIBT0OEyva/DrMjVm5HkQxKnHDLKEfEQBsEnwTg9HHhtPHJdTd8w==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.0.1.tgz", + "integrity": "sha512-SpwyojlnE/WRBNGtvJSNfllfm5PqEDFxcWluSIgLeSBJtXG4DmoX2NNAeEA7rP5kK+79VgtVq8nG6HskaL1ykg==", "dev": true, "requires": { "browser-stdout": "1.3.0", @@ -12964,7 +13182,12 @@ "integrity": "sha512-Yh9y73JRljxW5QxN08Fner68eFLxM5ynNOAw2LbIB1YAGeQzZT8QFSUvkAz609Zf+IHhhaUxqZK8dG3W/+HEvg==" }, "moment-recur": { - "version": "git://github.com/habitrpg/moment-recur.git#f147ef27bbc26ca67638385f3db4a44084c76626" + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/moment-recur/-/moment-recur-1.0.7.tgz", + "integrity": "sha1-TVCSr2SK7e1q/lwT7zjFKQHJlBk=", + "requires": { + "moment": "2.20.1" + } }, "mongodb": { "version": "2.2.34", @@ -13002,9 +13225,9 @@ } }, "mongoose": { - "version": "4.13.10", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-4.13.10.tgz", - "integrity": "sha512-ERmAyJf/ECyApfmkcjlbnmY0CoNzanOHA8vCIFuZwY55tW4b4gTVtklFXWLmSye8XjZjM0xHioXr5eN3Z8jtZg==", + "version": "4.13.11", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-4.13.11.tgz", + "integrity": "sha512-OgXmFc3vzXwq4zWp41XfSBDnKZLqnBc4Kh7mwwGjBE5iWH5tfkixaPK0uFtpEuzDzUvAIg33bgniyTsmc00olA==", "requires": { "async": "2.1.4", "bson": "1.0.4", @@ -13387,27 +13610,18 @@ } }, "nise": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/nise/-/nise-1.2.2.tgz", - "integrity": "sha512-rvxf+PSZeCKtP0DgmwMmNf1G3I6X1r4WHiP2H88PlIkOkt7mGqufdokjS8caoHBgZzVx0ee/5ytGcGHbZaUw8w==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/nise/-/nise-1.2.5.tgz", + "integrity": "sha512-Es4hGuq3lpip5PckrB+Qpuma282M0UJANJ+jxAgI+0wWTL9X6MtNv+M385JgqsAE8hv6NvD3lv8CQtXgEnvlpQ==", "dev": true, "requires": { - "formatio": "1.2.0", + "@sinonjs/formatio": "2.0.0", "just-extend": "1.1.27", - "lolex": "1.6.0", + "lolex": "2.3.2", "path-to-regexp": "1.7.0", "text-encoding": "0.6.4" }, "dependencies": { - "formatio": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/formatio/-/formatio-1.2.0.tgz", - "integrity": "sha1-87IWfZBoxGmKjVH092CjmlTYGOs=", - "dev": true, - "requires": { - "samsam": "1.1.2" - } - }, "isarray": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", @@ -13415,9 +13629,9 @@ "dev": true }, "lolex": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/lolex/-/lolex-1.6.0.tgz", - "integrity": "sha1-OpoCg0UqR9dDnnJzG54H1zhuSfY=", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-2.3.2.tgz", + "integrity": "sha512-A5pN2tkFj7H0dGIAM6MFvHKMJcPnjZsOMvR7ujCjfgW5TbV6H9vb1PgxLtHvjqNZTHsUolz+6/WEO0N1xNx2ng==", "dev": true }, "path-to-regexp": { @@ -13774,23 +13988,16 @@ "integrity": "sha1-WuVUHQJGRdMqWPzdyc7s6nrjrC8=" }, "nodemailer": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-2.7.2.tgz", - "integrity": "sha1-8kLmSa7q45tsftdA73sGHEBNMPk=", - "requires": { - "libmime": "3.0.0", - "mailcomposer": "4.0.1", - "nodemailer-direct-transport": "3.3.2", - "nodemailer-shared": "1.1.0", - "nodemailer-smtp-pool": "2.8.2", - "nodemailer-smtp-transport": "2.7.2", - "socks": "1.1.9" - } + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-4.5.0.tgz", + "integrity": "sha512-bznbREPZoRt3g400RdmVYXs2mbZft9Y1DfEqE2GS0VcBwU8DiEMDQ2q4gauSltbsnXtIVHtKH+VmE80I35TxhA==" }, "nodemailer-direct-transport": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/nodemailer-direct-transport/-/nodemailer-direct-transport-3.3.2.tgz", "integrity": "sha1-6W+vuQNYVglH5WkBfZfmBzilCoY=", + "dev": true, + "optional": true, "requires": { "nodemailer-shared": "1.1.0", "smtp-connection": "2.12.0" @@ -13799,12 +14006,14 @@ "nodemailer-fetch": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/nodemailer-fetch/-/nodemailer-fetch-1.6.0.tgz", - "integrity": "sha1-ecSQihwPXzdbc/6IjamCj23JY6Q=" + "integrity": "sha1-ecSQihwPXzdbc/6IjamCj23JY6Q=", + "dev": true }, "nodemailer-shared": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/nodemailer-shared/-/nodemailer-shared-1.1.0.tgz", "integrity": "sha1-z1mU4v0mjQD1zw+nZ6CBae2wfsA=", + "dev": true, "requires": { "nodemailer-fetch": "1.6.0" } @@ -13813,6 +14022,8 @@ "version": "2.8.2", "resolved": "https://registry.npmjs.org/nodemailer-smtp-pool/-/nodemailer-smtp-pool-2.8.2.tgz", "integrity": "sha1-LrlNbPhXgLG0clzoU7nL1ejajHI=", + "dev": true, + "optional": true, "requires": { "nodemailer-shared": "1.1.0", "nodemailer-wellknown": "0.1.10", @@ -13823,6 +14034,8 @@ "version": "2.7.2", "resolved": "https://registry.npmjs.org/nodemailer-smtp-transport/-/nodemailer-smtp-transport-2.7.2.tgz", "integrity": "sha1-A9ccdjFPFKx9vHvwM6am0W1n+3c=", + "dev": true, + "optional": true, "requires": { "nodemailer-shared": "1.1.0", "nodemailer-wellknown": "0.1.10", @@ -13832,7 +14045,8 @@ "nodemailer-wellknown": { "version": "0.1.10", "resolved": "https://registry.npmjs.org/nodemailer-wellknown/-/nodemailer-wellknown-0.1.10.tgz", - "integrity": "sha1-WG24EB2zDLRDjrVGc3pBqtDPE9U=" + "integrity": "sha1-WG24EB2zDLRDjrVGc3pBqtDPE9U=", + "dev": true }, "nodemon": { "version": "1.14.12", @@ -14361,45 +14575,29 @@ } }, "ora": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/ora/-/ora-1.4.0.tgz", - "integrity": "sha512-iMK1DOQxzzh2MBlVsU42G80mnrvUhqsMh74phHtDlrcTZPK0pH6o7l7DRshK+0YsxDyEuaOkziVdvM3T0QTzpw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ora/-/ora-2.0.0.tgz", + "integrity": "sha512-g+IR0nMUXq1k4nE3gkENbN4wkF0XsVZFyxznTF6CdmwQ9qeTGONGpSR9LM5//1l0TVvJoJF3MkMtJp6slUsWFg==", "requires": { - "chalk": "2.3.0", + "chalk": "2.3.1", "cli-cursor": "2.1.0", "cli-spinners": "1.1.0", - "log-symbols": "2.2.0" + "log-symbols": "2.2.0", + "strip-ansi": "4.0.0", + "wcwidth": "1.0.1" }, "dependencies": { - "ansi-styles": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", - "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", - "requires": { - "color-convert": "1.9.1" - } + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" }, - "chalk": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz", - "integrity": "sha512-Az5zJR2CBujap2rqXGaJKaPHyJ0IrUimvYNX+ncCy8PJP4ltOGTrHUIo097ZaL2zMeKYpiCdqDvS6zdrTFok3Q==", + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "requires": { - "ansi-styles": "3.2.0", - "escape-string-regexp": "1.0.5", - "supports-color": "4.5.0" - } - }, - "has-flag": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", - "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=" - }, - "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" + "ansi-regex": "3.0.0" } } } @@ -14478,11 +14676,21 @@ "os-tmpdir": "1.0.2" } }, + "p-cancelable": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-0.3.0.tgz", + "integrity": "sha512-RVbZPLso8+jFeq1MfNvgXtCRED2raz/dKpacfTNxsx6pLEpEomM7gah6VeHSYV3+vo0OAi4MkArtQcWWXuQoyw==" + }, "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": "1.1.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-1.1.0.tgz", + "integrity": "sha1-nJRWmJ6fZYgBewQ01WCXZ1w9oF4=" + }, "p-limit": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.2.0.tgz", @@ -14509,6 +14717,14 @@ "resolved": "https://registry.npmjs.org/p-pipe/-/p-pipe-1.2.0.tgz", "integrity": "sha1-SxoROZoRUgpneQ7loMHViB1r7+k=" }, + "p-timeout": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-2.0.1.tgz", + "integrity": "sha512-88em58dDVB/KzPEx1X0N3LwFfYZPyDc4B6eF38M1rk9VTZMbxXXgjugz8mmwpS9Ox4BDZ+t6t3QP5+/gazweIA==", + "requires": { + "p-finally": "1.0.0" + } + }, "p-try": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", @@ -14569,6 +14785,24 @@ "semver": "5.5.0" }, "dependencies": { + "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.2", + "duplexer3": "0.1.4", + "get-stream": "3.0.0", + "is-redirect": "1.0.0", + "is-retry-allowed": "1.1.0", + "is-stream": "1.1.0", + "lowercase-keys": "1.0.0", + "safe-buffer": "5.1.1", + "timed-out": "4.0.1", + "unzip-response": "2.0.1", + "url-parse-lax": "1.0.0" + } + }, "semver": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", @@ -16275,9 +16509,9 @@ "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" }, "puppeteer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-1.0.0.tgz", - "integrity": "sha512-e00NMdUL32YhBcua9OkVXHgyDEMBWJhDXkYNv0pyKRU1Z1OrsRm5zCpppAdxAsBI+/MJBspFNfOUZuZ24qPGMQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-1.1.0.tgz", + "integrity": "sha512-Up+EiDVtc+EueFUzEFi4JqTlkXdC4pjMng0W9s/uqg2HP7+EHJiIe8OtwP7I+Jk7rI1kS5WIVpqx46aihnLuJQ==", "dev": true, "requires": { "debug": "2.6.9", @@ -16320,12 +16554,6 @@ } } }, - "mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "dev": true - }, "progress": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.0.tgz", @@ -17227,6 +17455,14 @@ "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=" }, + "responselike": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", + "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=", + "requires": { + "lowercase-keys": "1.0.0" + } + }, "restore-cursor": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", @@ -17427,9 +17663,9 @@ } }, "selenium-server": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/selenium-server/-/selenium-server-3.9.0.tgz", - "integrity": "sha512-xbcL9yt/a1U0pvwAkneTWxrq7zF/X5oCAY5CMfwYbY3BglmvlXjrPhM5rnzVveDlVOtbXkZpcDnG3Ny9YUpddA==", + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/selenium-server/-/selenium-server-3.9.1.tgz", + "integrity": "sha512-p+GORe/uLnUUjMkEX3zqQmZKDxJ+WG7od92RKAh0Oz+H4e9Kjgxzgwb3E8IB4nGf2ifsZkJVo/uEvqRP30y9+Q==", "dev": true }, "semver": { @@ -17675,33 +17911,24 @@ "integrity": "sha1-BcLuxXn//+FFoDCsJs/qYbmA+r4=" }, "sinon": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-4.2.2.tgz", - "integrity": "sha512-BEa593xl+IkIc94nKo0O0LauQC/gQy8Gyv4DkzPwF/9DweC5phr1y+42zibCpn9abfkdHxt9r8AhD0R6u9DE/Q==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-4.3.0.tgz", + "integrity": "sha512-pmf05hFgEZUS52AGJcsVjOjqAyJW2yo14cOwVYvzCyw7+inv06YXkLyW75WG6X6p951lzkoKh51L2sNbR9CDvw==", "dev": true, "requires": { + "@sinonjs/formatio": "2.0.0", "diff": "3.2.0", - "formatio": "1.2.0", "lodash.get": "4.4.2", "lolex": "2.3.2", - "nise": "1.2.2", - "supports-color": "5.1.0", + "nise": "1.2.5", + "supports-color": "5.2.0", "type-detect": "4.0.8" }, "dependencies": { - "formatio": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/formatio/-/formatio-1.2.0.tgz", - "integrity": "sha1-87IWfZBoxGmKjVH092CjmlTYGOs=", - "dev": true, - "requires": { - "samsam": "1.1.2" - } - }, "has-flag": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", - "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", "dev": true }, "lolex": { @@ -17711,19 +17938,13 @@ "dev": true }, "supports-color": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.1.0.tgz", - "integrity": "sha512-Ry0AwkoKjDpVKK4sV4h6o3UJmNRbjYm2uXhwfj3J56lMVdvnUNqzQVRztOOMGQ++w1K/TjNDFvpJk0F/LoeBCQ==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.2.0.tgz", + "integrity": "sha512-F39vS48la4YvTZUPVeTqsjsFNrvcMwrV3RLZINsmHo+7djCvuUzSIeXOnZ5hmjef4bajL1dNccN+tg5XAliO5Q==", "dev": true, "requires": { - "has-flag": "2.0.0" + "has-flag": "3.0.0" } - }, - "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 } } }, @@ -17779,12 +18000,14 @@ "smart-buffer": { "version": "1.1.15", "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-1.1.15.tgz", - "integrity": "sha1-fxFLW2X6s+KjWqd1uxLw0cZJvxY=" + "integrity": "sha1-fxFLW2X6s+KjWqd1uxLw0cZJvxY=", + "dev": true }, "smtp-connection": { "version": "2.12.0", "resolved": "https://registry.npmjs.org/smtp-connection/-/smtp-connection-2.12.0.tgz", "integrity": "sha1-1275EnyyPCJZ7bHoNJwujV4tdME=", + "dev": true, "requires": { "httpntlm": "1.6.1", "nodemailer-shared": "1.1.0" @@ -17966,6 +18189,7 @@ "version": "1.1.9", "resolved": "https://registry.npmjs.org/socks/-/socks-1.1.9.tgz", "integrity": "sha1-Yo1+TQSRJDVEWsC25Fk3bLPm1pE=", + "dev": true, "requires": { "ip": "1.1.5", "smart-buffer": "1.1.15" @@ -18630,9 +18854,9 @@ "integrity": "sha1-IrD6OkE4WzO+PzMVUbu4N/oM164=" }, "stripe": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/stripe/-/stripe-5.4.0.tgz", - "integrity": "sha512-VCDFp4oQu1uOcOLHIwRIznH8ikLJcpDsHahWN48V/QuV6y2Bm281cq5wnkjqv+LPdUpqXVp9pjlb+SfN6dnyZg==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/stripe/-/stripe-5.5.0.tgz", + "integrity": "sha512-oy8xNoxv5/4eFsn+W/0KQvG9St7x/xM8TbOIW2prBLd2+zo+2y2V9iyqcNt6B14efcOE2YeCR1s5yw+1cqa2VQ==", "requires": { "bluebird": "3.5.1", "lodash.isplainobject": "4.0.6", @@ -18897,7 +19121,7 @@ "requires": { "ajv": "5.5.2", "ajv-keywords": "2.1.1", - "chalk": "2.3.0", + "chalk": "2.3.1", "lodash": "4.17.5", "slice-ansi": "1.0.0", "string-width": "2.1.1" @@ -19979,6 +20203,11 @@ "ip-regex": "1.0.3" } }, + "url-to-options": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/url-to-options/-/url-to-options-1.0.1.tgz", + "integrity": "sha1-FQWgOiiaSMvXpDTvuu7FBV9WM6k=" + }, "url2": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/url2/-/url2-1.0.4.tgz", @@ -20166,19 +20395,9 @@ "integrity": "sha1-fif8uzFbhB54JDQxiXZxkp4gt/Q=" }, "validator": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/validator/-/validator-4.9.0.tgz", - "integrity": "sha1-CC/84qdhSP8HqOienCukOq8S7Ew=", - "requires": { - "depd": "1.1.0" - }, - "dependencies": { - "depd": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.0.tgz", - "integrity": "sha1-4b2Cxqq2ztlluXuIsX7T5SjKGMM=" - } - } + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/validator/-/validator-9.4.1.tgz", + "integrity": "sha512-YV5KjzvRmSyJ1ee/Dm5UED0G+1L4GZnLN3w6/T+zZm8scVua4sOhYKWTUrKa0H/tMiJyO9QLHMPN+9mB/aMunA==" }, "value-or-function": { "version": "3.0.0", @@ -20463,9 +20682,9 @@ "integrity": "sha512-vLLoY452L+JBpALMP5UHum9+7nzR9PeIBCghU9ZtJ1eWm6ieUI8Zb/DI3MYxH32bxkjzYV1LRjNv4qr8d+uX/w==" }, "vue-style-loader": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/vue-style-loader/-/vue-style-loader-3.1.2.tgz", - "integrity": "sha512-ICtVdK/p+qXWpdSs2alWtsXt9YnDoYjQe0w5616j9+/EhjoxZkbun34uWgsMFnC1MhrMMwaWiImz3K2jK1Yp2Q==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/vue-style-loader/-/vue-style-loader-4.0.2.tgz", + "integrity": "sha512-Bwf1Gf331Z5OTzMRAYQYiFpFbaCpaXQjQcSvWYsmEwSgOIVa+moXWoD8fQCNetcekbP3OSE5pyvomNKbvIUQtQ==", "requires": { "hash-sum": "1.0.2", "loader-utils": "1.1.0" @@ -20516,6 +20735,26 @@ "cheerio": "0.19.0", "got": "6.7.1", "meow": "3.7.0" + }, + "dependencies": { + "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.2", + "duplexer3": "0.1.4", + "get-stream": "3.0.0", + "is-redirect": "1.0.0", + "is-retry-allowed": "1.1.0", + "is-stream": "1.1.0", + "lowercase-keys": "1.0.0", + "safe-buffer": "5.1.1", + "timed-out": "4.0.1", + "unzip-response": "2.0.1", + "url-parse-lax": "1.0.0" + } + } } }, "ware": { @@ -20659,6 +20898,14 @@ } } }, + "wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=", + "requires": { + "defaults": "1.0.3" + } + }, "webpack": { "version": "3.11.0", "resolved": "https://registry.npmjs.org/webpack/-/webpack-3.11.0.tgz", @@ -21122,7 +21369,7 @@ "integrity": "sha512-B53SD4N4BHpZdUwZcj4st2QT7gVfqZtqHDruC1N+K2sciq0Rt/3F1Dx6RlylVkcrToMLTaiaeT48k9Lq4iDVDA==", "dev": true, "requires": { - "chalk": "2.3.0", + "chalk": "2.3.1", "log-symbols": "2.2.0", "loglevelnext": "1.0.3", "uuid": "3.2.1" @@ -21377,6 +21624,11 @@ "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.4.tgz", "integrity": "sha1-UZy0ymhtAFqEINNJbz8MruzKWA8=" }, + "xmlcreate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-1.0.2.tgz", + "integrity": "sha1-+mv3YqYKQT+z3Y9LA8WyaSONMI8=" + }, "xmldom": { "version": "0.1.19", "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.1.19.tgz", diff --git a/package.json b/package.json index 05a5956316..b435d3fa9c 100644 --- a/package.json +++ b/package.json @@ -9,14 +9,13 @@ "amazon-payments": "^0.2.6", "amplitude": "^3.5.0", "apidoc": "^0.17.5", - "apn": "^1.7.6", "autoprefixer": "^8.0.0", - "aws-sdk": "^2.0.25", - "axios": "^0.17.1", + "aws-sdk": "^2.200.0", + "axios": "^0.18.0", "axios-progress-bar": "^1.1.8", "babel-core": "^6.0.0", + "babel-eslint": "^8.2.2", "babel-loader": "^7.1.2", - "babel-eslint": "^8.2.1", "babel-plugin-syntax-async-functions": "^6.13.0", "babel-plugin-syntax-dynamic-import": "^6.18.0", "babel-plugin-transform-async-to-module-method": "^6.8.0", @@ -31,20 +30,20 @@ "body-parser": "^1.15.0", "bootstrap": "^4.0.0", "bootstrap-vue": "^2.0.0-rc.1", - "compression": "^1.6.1", + "compression": "^1.7.2", "cookie-session": "^1.2.0", "coupon-code": "^0.4.5", "cross-env": "^5.1.3", "css-loader": "^0.28.0", - "csv-stringify": "^2.0.1", + "csv-stringify": "^2.0.4", "cwait": "^1.1.1", "domain-middleware": "~0.1.0", "express": "^4.16.2", - "express-basic-auth": "^1.0.1", - "express-validator": "^2.18.0", + "express-basic-auth": "^1.1.4", + "express-validator": "^5.0.1", "extract-text-webpack-plugin": "^3.0.2", "glob": "^7.1.2", - "got": "^6.1.1", + "got": "^8.2.0", "gulp": "^4.0.0", "gulp-babel": "^7.0.1", "gulp-imagemin": "^4.1.0", @@ -57,19 +56,19 @@ "in-app-purchase": "^1.1.6", "intro.js": "^2.6.0", "jquery": ">=3.0.0", - "js2xmlparser": "~1.0.0", + "js2xmlparser": "^3.0.0", "lodash": "^4.17.4", "merge-stream": "^1.0.0", "method-override": "^2.3.5", "moment": "^2.13.0", - "moment-recur": "git://github.com/habitrpg/moment-recur.git#f147ef27bbc26ca67638385f3db4a44084c76626", - "mongoose": "^4.13.10", + "moment-recur": "^1.0.7", + "mongoose": "^4.13.11", "morgan": "^1.7.0", "nconf": "^0.10.0", "node-gcm": "^0.14.4", "node-sass": "^4.5.0", - "nodemailer": "^2.3.2", - "ora": "^1.1.0", + "nodemailer": "^4.5.0", + "ora": "^2.0.0", "pageres": "^4.1.1", "passport": "^0.4.0", "passport-facebook": "^2.0.0", @@ -82,11 +81,10 @@ "pug": "^2.0.0-rc.4", "push-notify": "git://github.com/habitrpg/push-notify.git#6bc2b5fdb1bdc9649b9ec1964d79ca50187fc8a9", "pusher": "^1.3.0", - "request": "^2.83.0", "rimraf": "^2.4.3", "sass-loader": "^6.0.2", "shelljs": "^0.8.1", - "stripe": "^5.4.0", + "stripe": "^5.5.0", "superagent": "^3.4.3", "svg-inline-loader": "^0.8.0", "svg-url-loader": "^2.0.2", @@ -96,13 +94,13 @@ "url-loader": "^0.6.2", "useragent": "^2.1.9", "uuid": "^3.0.1", - "validator": "^4.9.0", + "validator": "^9.4.1", "vinyl-buffer": "^1.0.1", "vue": "^2.5.2", "vue-loader": "^14.1.1", "vue-mugen-scroll": "^0.2.1", "vue-router": "^3.0.0", - "vue-style-loader": "^3.0.0", + "vue-style-loader": "^4.0.2", "vue-template-compiler": "^2.5.2", "vuedraggable": "^2.15.0", "vuejs-datepicker": "git://github.com/habitrpg/vuejs-datepicker.git#5d237615463a84a23dd6f3f77c6ab577d68593ec", @@ -145,12 +143,12 @@ "babel-plugin-istanbul": "^4.0.0", "chai": "^4.1.2", "chai-as-promised": "^7.1.1", - "chalk": "^2.3.0", + "chalk": "^2.3.1", "chromedriver": "^2.27.2", "connect-history-api-fallback": "^1.1.0", "coveralls": "^3.0.0", "cross-spawn": "^6.0.4", - "eslint": "^4.17.0", + "eslint": "^4.18.1", "eslint-config-habitrpg": "^4.0.0", "eslint-friendly-formatter": "^3.0.0", "eslint-loader": "^1.3.0", @@ -173,13 +171,13 @@ "karma-spec-reporter": "0.0.32", "karma-webpack": "^2.0.2", "lcov-result-merger": "^2.0.0", - "mocha": "^5.0.0", + "mocha": "^5.0.1", "monk": "^6.0.5", "nightwatch": "^0.9.12", - "puppeteer": "^1.0.0", + "puppeteer": "^1.1.0", "require-again": "^2.0.0", - "selenium-server": "^3.0.1", - "sinon": "^4.2.2", + "selenium-server": "^3.9.1", + "sinon": "^4.3.0", "sinon-chai": "^2.8.0", "sinon-stub-promise": "^4.0.0", "webpack-bundle-analyzer": "^2.2.1", diff --git a/test/api/v3/integration/chat/POST-chat.test.js b/test/api/v3/integration/chat/POST-chat.test.js index 8892c8e845..c9b2d97384 100644 --- a/test/api/v3/integration/chat/POST-chat.test.js +++ b/test/api/v3/integration/chat/POST-chat.test.js @@ -478,8 +478,8 @@ describe('POST /chat', () => { context('Spam prevention', () => { it('Returns an error when the user has been posting too many messages', async () => { // Post as many messages are needed to reach the spam limit - for (let i = 0; i < SPAM_MESSAGE_LIMIT; i++) { // eslint-disable-line no-await-in-loop - let result = await additionalMember.post(`/groups/${TAVERN_ID}/chat`, { message: testMessage }); + for (let i = 0; i < SPAM_MESSAGE_LIMIT; i++) { + let result = await additionalMember.post(`/groups/${TAVERN_ID}/chat`, { message: testMessage }); // eslint-disable-line no-await-in-loop expect(result.message.id).to.exist; } @@ -494,8 +494,8 @@ describe('POST /chat', () => { let userSocialite = await member.update({'contributor.level': SPAM_MIN_EXEMPT_CONTRIB_LEVEL, 'flags.chatRevoked': false}); // Post 1 more message than the spam limit to ensure they do not reach the limit - for (let i = 0; i < SPAM_MESSAGE_LIMIT + 1; i++) { // eslint-disable-line no-await-in-loop - let result = await userSocialite.post(`/groups/${TAVERN_ID}/chat`, { message: testMessage }); + for (let i = 0; i < SPAM_MESSAGE_LIMIT + 1; i++) { + let result = await userSocialite.post(`/groups/${TAVERN_ID}/chat`, { message: testMessage }); // eslint-disable-line no-await-in-loop expect(result.message.id).to.exist; } }); diff --git a/test/api/v3/integration/tasks/GET-tasks_user.test.js b/test/api/v3/integration/tasks/GET-tasks_user.test.js index 0525f6b4b5..ea8f2a49d0 100644 --- a/test/api/v3/integration/tasks/GET-tasks_user.test.js +++ b/test/api/v3/integration/tasks/GET-tasks_user.test.js @@ -113,10 +113,10 @@ describe('GET /tasks/user', () => { await user.sync(); let initialTodoCount = user.tasksOrder.todos.length; - for (let i = 0; i < numberOfTodos; i++) { // eslint-disable-line no-await-in-loop + for (let i = 0; i < numberOfTodos; i++) { let id = todos[i]._id; - await user.post(`/tasks/${id}/score/up`); + await user.post(`/tasks/${id}/score/up`); // eslint-disable-line no-await-in-loop } await user.sync(); diff --git a/test/api/v3/integration/tasks/POST-tasks_clearCompletedTodos.test.js b/test/api/v3/integration/tasks/POST-tasks_clearCompletedTodos.test.js index f3c79fe868..7cdd7753d2 100644 --- a/test/api/v3/integration/tasks/POST-tasks_clearCompletedTodos.test.js +++ b/test/api/v3/integration/tasks/POST-tasks_clearCompletedTodos.test.js @@ -33,9 +33,9 @@ describe('POST /tasks/clearCompletedTodos', () => { let tasks = await user.get('/tasks/user?type=todos'); expect(tasks.length).to.equal(initialTodoCount + 7); - for (let task of tasks) { // eslint-disable-line no-await-in-loop + for (let task of tasks) { if (['todo 2', 'todo 3', 'todo 6'].indexOf(task.text) !== -1) { - await user.post(`/tasks/${task._id}/score/up`); + await user.post(`/tasks/${task._id}/score/up`); // eslint-disable-line no-await-in-loop } } diff --git a/test/api/v3/integration/webhook/PUT-user_update_webhook.test.js b/test/api/v3/integration/webhook/PUT-user_update_webhook.test.js index 2e02562b5f..994eff6750 100644 --- a/test/api/v3/integration/webhook/PUT-user_update_webhook.test.js +++ b/test/api/v3/integration/webhook/PUT-user_update_webhook.test.js @@ -34,7 +34,7 @@ describe('PUT /user/webhook/:id', () => { }); it('returns an error if validation fails', async () => { - await expect(user.put(`/user/webhook/${webhookToUpdate.id}`, { url: 'foo', enabled: true })).to.eventually.be.rejected.and.eql({ + await expect(user.put(`/user/webhook/${webhookToUpdate.id}`, { url: 'foo_invalid', enabled: true })).to.eventually.be.rejected.and.eql({ code: 400, error: 'BadRequest', message: 'User validation failed', diff --git a/test/api/v3/unit/libs/email.test.js b/test/api/v3/unit/libs/email.test.js index 1597ccee73..ebfa2ac6ea 100644 --- a/test/api/v3/unit/libs/email.test.js +++ b/test/api/v3/unit/libs/email.test.js @@ -1,27 +1,11 @@ /* eslint-disable global-require */ -import request from 'request'; +import got from 'got'; import nconf from 'nconf'; import nodemailer from 'nodemailer'; -import Bluebird from 'bluebird'; import requireAgain from 'require-again'; import logger from '../../../../../website/server/libs/logger'; import { TAVERN_ID } from '../../../../../website/server/models/group'; - -function defer () { - let resolve; - let reject; - - let promise = new Bluebird((resolveParam, rejectParam) => { - resolve = resolveParam; - reject = rejectParam; - }); - - return { - resolve, - reject, - promise, - }; -} +import { defer } from '../../../../helpers/api-unit.helper'; function getUser () { return { @@ -158,7 +142,7 @@ describe('emails', () => { describe('sendTxnEmail', () => { beforeEach(() => { - sandbox.stub(request, 'post'); + sandbox.stub(got, 'post').returns(defer().promise); }); afterEach(() => { @@ -176,8 +160,9 @@ describe('emails', () => { }; sendTxnEmail(mailingInfo, emailType); - expect(request.post).to.be.calledWith(sinon.match({ - json: { + expect(got.post).to.be.calledWith('undefined/job', sinon.match({ + json: true, + body: { data: { emailType: sinon.match.same(emailType), to: sinon.match((value) => { @@ -199,7 +184,7 @@ describe('emails', () => { }; sendTxnEmail(mailingInfo, emailType); - expect(request.post).not.to.be.called; + expect(got.post).not.to.be.called; }); it('uses getUserInfo in case of user data', () => { @@ -210,8 +195,9 @@ describe('emails', () => { let mailingInfo = getUser(); sendTxnEmail(mailingInfo, emailType); - expect(request.post).to.be.calledWith(sinon.match({ - json: { + expect(got.post).to.be.calledWith('undefined/job', sinon.match({ + json: true, + body: { data: { emailType: sinon.match.same(emailType), to: sinon.match(val => val[0]._id === mailingInfo._id), @@ -232,8 +218,9 @@ describe('emails', () => { let variables = [1, 2, 3]; sendTxnEmail(mailingInfo, emailType, variables); - expect(request.post).to.be.calledWith(sinon.match({ - json: { + expect(got.post).to.be.calledWith('undefined/job', sinon.match({ + json: true, + body: { data: { variables: sinon.match((value) => { return value[0].name === 'BASE_URL'; diff --git a/test/api/v3/unit/libs/webhooks.test.js b/test/api/v3/unit/libs/webhooks.test.js index 49c3d0e24f..467ce9bd8a 100644 --- a/test/api/v3/unit/libs/webhooks.test.js +++ b/test/api/v3/unit/libs/webhooks.test.js @@ -1,16 +1,17 @@ -import request from 'request'; +import got from 'got'; import { WebhookSender, taskScoredWebhook, groupChatReceivedWebhook, taskActivityWebhook, } from '../../../../../website/server/libs/webhook'; +import { defer } from '../../../../helpers/api-unit.helper'; describe('webhooks', () => { let webhooks; beforeEach(() => { - sandbox.stub(request, 'post'); + sandbox.stub(got, 'post').returns(defer().promise); webhooks = [{ id: 'taskActivity', @@ -59,8 +60,9 @@ describe('webhooks', () => { sendWebhook.send([{id: 'custom-webhook', url: 'http://custom-url.com', enabled: true, type: 'custom'}], body); expect(WebhookSender.defaultTransformData).to.be.calledOnce; - expect(request.post).to.be.calledOnce; - expect(request.post).to.be.calledWithMatch({ + expect(got.post).to.be.calledOnce; + expect(got.post).to.be.calledWithMatch('http://custom-url.com', { + json: true, body, }); }); @@ -81,8 +83,9 @@ describe('webhooks', () => { sendWebhook.send([{id: 'custom-webhook', url: 'http://custom-url.com', enabled: true, type: 'custom'}], body); expect(WebhookSender.defaultTransformData).to.not.be.called; - expect(request.post).to.be.calledOnce; - expect(request.post).to.be.calledWithMatch({ + expect(got.post).to.be.calledOnce; + expect(got.post).to.be.calledWithMatch('http://custom-url.com', { + json: true, body: { foo: 'bar', baz: 'biz', @@ -117,7 +120,7 @@ describe('webhooks', () => { sendWebhook.send([{id: 'custom-webhook', url: 'http://custom-url.com', enabled: true, type: 'custom'}], body); expect(WebhookSender.defaultWebhookFilter).to.not.be.called; - expect(request.post).to.not.be.called; + expect(got.post).to.not.be.called; }); it('can pass in a webhook filter function that filters on data', () => { @@ -136,10 +139,8 @@ describe('webhooks', () => { { id: 'other-custom-webhook', url: 'http://other-custom-url.com', enabled: true, type: 'custom', options: { foo: 'foo' }}, ], body); - expect(request.post).to.be.calledOnce; - expect(request.post).to.be.calledWithMatch({ - url: 'http://custom-url.com', - }); + expect(got.post).to.be.calledOnce; + expect(got.post).to.be.calledWithMatch('http://custom-url.com'); }); it('ignores disabled webhooks', () => { @@ -151,7 +152,7 @@ describe('webhooks', () => { sendWebhook.send([{id: 'custom-webhook', url: 'http://custom-url.com', enabled: false, type: 'custom'}], body); - expect(request.post).to.not.be.called; + expect(got.post).to.not.be.called; }); it('ignores webhooks with invalid urls', () => { @@ -163,7 +164,7 @@ describe('webhooks', () => { sendWebhook.send([{id: 'custom-webhook', url: 'httxp://custom-url!!', enabled: true, type: 'custom'}], body); - expect(request.post).to.not.be.called; + expect(got.post).to.not.be.called; }); it('ignores webhooks of other types', () => { @@ -178,9 +179,8 @@ describe('webhooks', () => { { id: 'other-webhook', url: 'http://other-url.com', enabled: true, type: 'other'}, ], body); - expect(request.post).to.be.calledOnce; - expect(request.post).to.be.calledWithMatch({ - url: 'http://custom-url.com', + expect(got.post).to.be.calledOnce; + expect(got.post).to.be.calledWithMatch('http://custom-url.com', { body, json: true, }); @@ -198,14 +198,12 @@ describe('webhooks', () => { { id: 'other-custom-webhook', url: 'http://other-url.com', enabled: true, type: 'custom'}, ], body); - expect(request.post).to.be.calledTwice; - expect(request.post).to.be.calledWithMatch({ - url: 'http://custom-url.com', + expect(got.post).to.be.calledTwice; + expect(got.post).to.be.calledWithMatch('http://custom-url.com', { body, json: true, }); - expect(request.post).to.be.calledWithMatch({ - url: 'http://other-url.com', + expect(got.post).to.be.calledWithMatch('http://other-url.com', { body, json: true, }); @@ -252,8 +250,9 @@ describe('webhooks', () => { it('sends task and stats data', () => { taskScoredWebhook.send(webhooks, data); - expect(request.post).to.be.calledOnce; - expect(request.post).to.be.calledWithMatch({ + expect(got.post).to.be.calledOnce; + expect(got.post).to.be.calledWithMatch(webhooks[0].url, { + json: true, body: { type: 'scored', user: { @@ -283,7 +282,7 @@ describe('webhooks', () => { taskScoredWebhook.send(webhooks, data); - expect(request.post).to.not.be.called; + expect(got.post).to.not.be.called; }); }); @@ -304,8 +303,9 @@ describe('webhooks', () => { taskActivityWebhook.send(webhooks, data); - expect(request.post).to.be.calledOnce; - expect(request.post).to.be.calledWithMatch({ + expect(got.post).to.be.calledOnce; + expect(got.post).to.be.calledWithMatch(webhooks[0].url, { + json: true, body: { type, task: data.task, @@ -319,7 +319,7 @@ describe('webhooks', () => { taskActivityWebhook.send(webhooks, data); - expect(request.post).to.not.be.called; + expect(got.post).to.not.be.called; }); }); }); @@ -340,8 +340,9 @@ describe('webhooks', () => { groupChatReceivedWebhook.send(webhooks, data); - expect(request.post).to.be.calledOnce; - expect(request.post).to.be.calledWithMatch({ + expect(got.post).to.be.calledOnce; + expect(got.post).to.be.calledWithMatch(webhooks[webhooks.length - 1].url, { + json: true, body: { group: { id: 'group-id', @@ -370,7 +371,7 @@ describe('webhooks', () => { groupChatReceivedWebhook.send(webhooks, data); - expect(request.post).to.not.be.called; + expect(got.post).to.not.be.called; }); }); }); diff --git a/test/helpers/api-unit.helper.js b/test/helpers/api-unit.helper.js index ebfea46dad..d32423dfb9 100644 --- a/test/helpers/api-unit.helper.js +++ b/test/helpers/api-unit.helper.js @@ -108,3 +108,19 @@ export function generateDaily (user) { return task; } + +export function defer () { + let resolve; + let reject; + + let promise = new Promise((resolveParam, rejectParam) => { + resolve = resolveParam; + reject = rejectParam; + }); + + return { + resolve, + reject, + promise, + }; +} diff --git a/website/client/store/actions/user.js b/website/client/store/actions/user.js index 07d7d599a6..35402430f9 100644 --- a/website/client/store/actions/user.js +++ b/website/client/store/actions/user.js @@ -21,7 +21,7 @@ export function fetch (store, options = {}) { // eslint-disable-line no-shadow export async function set (store, changes) { const user = store.state.user.data; - for (let key in changes) { // eslint-disable-line no-await-in-loop + for (let key in changes) { if (key === 'tags') { // Keep challenge and group tags const oldTags = user.tags.filter(t => { @@ -31,7 +31,7 @@ export async function set (store, changes) { user.tags = changes[key].concat(oldTags); // Remove deleted tags from tasks - const userTasksByType = (await store.dispatch('tasks:fetchUserTasks')).data; + const userTasksByType = (await store.dispatch('tasks:fetchUserTasks')).data; // eslint-disable-line no-await-in-loop Object.keys(userTasksByType).forEach(taskType => { userTasksByType[taskType].forEach(task => { diff --git a/website/server/controllers/api-v3/auth.js b/website/server/controllers/api-v3/auth.js index ddb4ccbfcc..542e77b081 100644 --- a/website/server/controllers/api-v3/auth.js +++ b/website/server/controllers/api-v3/auth.js @@ -247,7 +247,7 @@ api.loginLocal = { let username = req.body.username; let password = req.body.password; - if (validator.isEmail(username)) { + if (validator.isEmail(String(username))) { login = {'auth.local.email': username.toLowerCase()}; // Emails are stored lowercase } else { login = {'auth.local.username': username}; @@ -410,7 +410,7 @@ api.pusherAuth = { } resourceId = resourceId.join('-'); // the split at the beginning had split resourceId too - if (!validator.isUUID(resourceId)) { + if (!validator.isUUID(String(resourceId))) { throw new BadRequest('Invalid Pusher resource id, must be a UUID.'); } diff --git a/website/server/controllers/top-level/dataexport.js b/website/server/controllers/top-level/dataexport.js index d8d63f01a3..9f075b27ab 100644 --- a/website/server/controllers/top-level/dataexport.js +++ b/website/server/controllers/top-level/dataexport.js @@ -144,7 +144,12 @@ api.exportUserDataXml = { 'Content-Type': 'text/xml', 'Content-disposition': 'attachment; filename=habitica-user-data.xml', }); - res.status(200).send(js2xml('user', userData)); + res.status(200).send(js2xml.parse('user', userData, { + cdataInvalidChars: true, + declaration: { + include: false, + }, + })); }, }; diff --git a/website/server/libs/applePayments.js b/website/server/libs/applePayments.js index 4cb0f0e76f..e77f452a78 100644 --- a/website/server/libs/applePayments.js +++ b/website/server/libs/applePayments.js @@ -33,16 +33,16 @@ api.verifyGemPurchase = async function verifyGemPurchase (user, receipt, headers let correctReceipt = false; // Purchasing one item at a time (processing of await(s) below is sequential not parallel) - for (let index in purchaseDataList) { // eslint-disable-line no-await-in-loop + for (let index in purchaseDataList) { let purchaseData = purchaseDataList[index]; let token = purchaseData.transactionId; - let existingReceipt = await IapPurchaseReceipt.findOne({ + let existingReceipt = await IapPurchaseReceipt.findOne({ // eslint-disable-line no-await-in-loop _id: token, }).exec(); if (!existingReceipt) { - await IapPurchaseReceipt.create({ + await IapPurchaseReceipt.create({ // eslint-disable-line no-await-in-loop _id: token, consumed: true, userId: user._id, diff --git a/website/server/libs/email.js b/website/server/libs/email.js index ed53e7e315..330f2d2ac4 100644 --- a/website/server/libs/email.js +++ b/website/server/libs/email.js @@ -1,8 +1,8 @@ -import { createTransport } from 'nodemailer'; +import nodemailer from 'nodemailer'; import nconf from 'nconf'; import { TAVERN_ID } from '../models/group'; import { encrypt } from './encryption'; -import request from 'request'; +import got from 'got'; import logger from './logger'; import common from '../../common'; @@ -16,7 +16,7 @@ const EMAIL_SERVER = { }; const BASE_URL = nconf.get('BASE_URL'); -let smtpTransporter = createTransport({ +let smtpTransporter = nodemailer.createTransport({ service: nconf.get('SMTP_SERVICE'), auth: { user: nconf.get('SMTP_USER'), @@ -150,13 +150,10 @@ export function sendTxn (mailingInfoArray, emailType, variables, personalVariabl } if (IS_PROD && mailingInfoArray.length > 0) { - request.post({ - url: `${EMAIL_SERVER.url}/job`, - auth: { - user: EMAIL_SERVER.auth.user, - pass: EMAIL_SERVER.auth.password, - }, - json: { + got.post(`${EMAIL_SERVER.url}/job`, { + auth: `${EMAIL_SERVER.auth.user}:${EMAIL_SERVER.auth.password}`, + json: true, + body: { type: 'email', data: { emailType, @@ -170,6 +167,6 @@ export function sendTxn (mailingInfoArray, emailType, variables, personalVariabl backoff: {delay: 10 * 60 * 1000, type: 'fixed'}, }, }, - }, (err) => logger.error(err)); + }).catch((err) => logger.error(err)); } } diff --git a/website/server/libs/pushNotifications.js b/website/server/libs/pushNotifications.js index e7071c03bc..7e31d76660 100644 --- a/website/server/libs/pushNotifications.js +++ b/website/server/libs/pushNotifications.js @@ -1,8 +1,7 @@ import _ from 'lodash'; import nconf from 'nconf'; -// TODO remove this lib and use directly the apn module +// @TODO remove this lib and use directly the apn module import pushNotify from 'push-notify'; -import apnLib from 'apn'; import logger from './logger'; import Bluebird from 'bluebird'; import { @@ -44,21 +43,9 @@ if (APN_ENABLED) { apn.on('transmissionError', (errorCode, notification, device) => { logger.error('APN transmissionError', errorCode, notification, device); }); - - let feedback = new apnLib.Feedback({ - key, - cert, - batchFeedback: true, - interval: 3600, // Check for feedback once an hour - }); - - feedback.on('feedback', (devices) => { - if (devices && devices.length > 0) { - logger.info('Delivery of push notifications failed for some Apple devices.', devices); - } - }); }); } + function sendNotification (user, details = {}) { if (!user) throw new Error('User is required.'); if (user.preferences.pushNotifications.unsubscribeFromAll === true) return; diff --git a/website/server/libs/webhook.js b/website/server/libs/webhook.js index bbe2ba8f45..4265abf9ba 100644 --- a/website/server/libs/webhook.js +++ b/website/server/libs/webhook.js @@ -1,21 +1,21 @@ -import { post } from 'request'; +import got from 'got'; import { isURL } from 'validator'; import logger from './logger'; +import nconf from 'nconf'; + +const IS_PRODUCTION = nconf.get('IS_PROD'); function sendWebhook (url, body) { - post({ - url, + got.post(url, { body, json: true, - }, (err) => { - if (err) { - logger.error(err); - } - }); + }).catch(err => logger.error(err)); } function isValidWebhook (hook) { - return hook.enabled && isURL(hook.url); + return hook.enabled && isURL(hook.url, { + require_tld: IS_PRODUCTION ? true : false, // eslint-disable-line camelcase + }); } export class WebhookSender { diff --git a/website/server/models/task.js b/website/server/models/task.js index ee3bdc538e..866d3b5c68 100644 --- a/website/server/models/task.js +++ b/website/server/models/task.js @@ -144,7 +144,7 @@ TaskSchema.statics.findByIdOrAlias = async function findByIdOrAlias (identifier, let query = _.cloneDeep(additionalQueries); - if (validator.isUUID(identifier)) { + if (validator.isUUID(String(identifier))) { query._id = identifier; } else { query.userId = userId; diff --git a/website/server/models/webhook.js b/website/server/models/webhook.js index 19522bd491..7398592080 100644 --- a/website/server/models/webhook.js +++ b/website/server/models/webhook.js @@ -5,7 +5,9 @@ import shared from '../../common'; import {v4 as uuid} from 'uuid'; import _ from 'lodash'; import { BadRequest } from '../libs/errors'; +import nconf from 'nconf'; +const IS_PRODUCTION = nconf.get('IS_PROD'); const Schema = mongoose.Schema; const TASK_ACTIVITY_DEFAULT_OPTIONS = Object.freeze({ @@ -36,7 +38,11 @@ export let schema = new Schema({ url: { type: String, required: true, - validate: [validator.isURL, shared.i18n.t('invalidUrl')], + validate: [(v) => { + return validator.isURL(v, { + require_tld: IS_PRODUCTION ? true : false, // eslint-disable-line camelcase + }); + }, shared.i18n.t('invalidUrl')], }, enabled: { type: Boolean, required: true, default: true }, options: { @@ -72,7 +78,7 @@ schema.methods.formatOptions = function formatOptions (res) { } else if (this.type === 'groupChatReceived') { this.options = _.pick(this.options, 'groupId'); - if (!validator.isUUID(this.options.groupId)) { + if (!validator.isUUID(String(this.options.groupId))) { throw new BadRequest(res.t('groupIdRequired')); } } From 94e615e53e778f7a0666ada62e9479894a9fc5bf Mon Sep 17 00:00:00 2001 From: README Bot <35302948+codetriage-readme-bot@users.noreply.github.com> Date: Fri, 23 Feb 2018 08:47:09 -0600 Subject: [PATCH 02/15] Add CodeTriage badge to habitrpg/habitica (#10028) Adds a badge showing the number of people helping this repo on CodeTriage. [![Open Source Helpers](https://www.codetriage.com/habitrpg/habitica/badges/users.svg)](https://www.codetriage.com/habitrpg/habitica) CodeTriage is an Open Source app that is designed to make contributing to Open Source projects easier. It works by sending subscribers a few open issues in their inbox. If subscribers get busy, there is an algorithm that backs off issue load so they do not get overwhelmed [Read more about the CodeTriage project](https://www.codetriage.com/what). Your project was picked by the human, @schneems. They selected it from the projects submitted to https://www.codetriage.com and hand edited the PR. How did your project get added to [CodeTriage](https://www.codetriage.com/what)? Roughly 11 months ago, [chufty](https://github.com/chufty) added this project to CodeTriage in order to start contributing. Since then, 29 people have subscribed to help this repo. Adding a badge invites people to help contribute to your project. It also lets developers know that others are invested in the longterm success and maintainability of the project. You can see an example of a CodeTriage badge on these popular OSS READMEs: - [![](https://www.codetriage.com/rails/rails/badges/users.svg)](https://www.codetriage.com/rails/rails) https://github.com/rails/rails - [![](https://www.codetriage.com/crystal-lang/crystal/badges/users.svg)](https://www.codetriage.com/crystal-lang/crystal) https://github.com/crystal-lang/crystal While I am a bot, this PR was manually reviewed and monitored by a human - @schneems. My job is writing commit messages and handling PR logistics. If you have any questions, you can reply back to this PR and they will be answered by @schneems. If you do not want a badge right now, no worries, close the PR, you will not hear from me again. Thanks for making your project Open Source! Any feedback is greatly appreciated. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 85dc8c0edb..07d59ebcbc 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -Habitica [![Build Status](https://travis-ci.org/HabitRPG/habitica.svg?branch=develop)](https://travis-ci.org/HabitRPG/habitica) [![Code Climate](https://codeclimate.com/github/HabitRPG/habitrpg.svg)](https://codeclimate.com/github/HabitRPG/habitrpg) [![Coverage Status](https://coveralls.io/repos/github/HabitRPG/habitica/badge.svg?branch=develop)](https://coveralls.io/github/HabitRPG/habitica?branch=develop) [![Bountysource](https://api.bountysource.com/badge/tracker?tracker_id=68393)](https://www.bountysource.com/trackers/68393-habitrpg?utm_source=68393&utm_medium=shield&utm_campaign=TRACKER_BADGE) +Habitica [![Build Status](https://travis-ci.org/HabitRPG/habitica.svg?branch=develop)](https://travis-ci.org/HabitRPG/habitica) [![Code Climate](https://codeclimate.com/github/HabitRPG/habitrpg.svg)](https://codeclimate.com/github/HabitRPG/habitrpg) [![Coverage Status](https://coveralls.io/repos/github/HabitRPG/habitica/badge.svg?branch=develop)](https://coveralls.io/github/HabitRPG/habitica?branch=develop) [![Bountysource](https://api.bountysource.com/badge/tracker?tracker_id=68393)](https://www.bountysource.com/trackers/68393-habitrpg?utm_source=68393&utm_medium=shield&utm_campaign=TRACKER_BADGE) [![Open Source Helpers](https://www.codetriage.com/habitrpg/habitica/badges/users.svg)](https://www.codetriage.com/habitrpg/habitica) =============== [Habitica](https://habitica.com) is an open source habit building program which treats your life like a Role Playing Game. Level up as you succeed, lose HP as you fail, earn money to buy weapons and armor. From b850ea9dbfdc9778101652dcfdce3987c7c7bfad Mon Sep 17 00:00:00 2001 From: Keith Holliday Date: Fri, 23 Feb 2018 11:30:15 -0700 Subject: [PATCH 03/15] Bulk spell casting (#10007) * Reorganized spell code * Added quantity field * Fixed parameter passing --- .../user/POST-user_class_cast_spellId.test.js | 16 ++ website/server/controllers/api-v3/user.js | 222 +----------------- .../server/controllers/api-v3/user/spells.js | 140 +++++++++++ website/server/libs/spells.js | 121 ++++++++++ 4 files changed, 278 insertions(+), 221 deletions(-) create mode 100644 website/server/controllers/api-v3/user/spells.js create mode 100644 website/server/libs/spells.js diff --git a/test/api/v3/integration/user/POST-user_class_cast_spellId.test.js b/test/api/v3/integration/user/POST-user_class_cast_spellId.test.js index 9453757530..d8cd074bc7 100644 --- a/test/api/v3/integration/user/POST-user_class_cast_spellId.test.js +++ b/test/api/v3/integration/user/POST-user_class_cast_spellId.test.js @@ -187,6 +187,22 @@ describe('POST /user/class/cast/:spellId', () => { expect(group.chat[0].uuid).to.equal('system'); }); + it('cast bulk', async () => { + let { group, groupLeader } = await createAndPopulateGroup({ + groupDetails: { type: 'party', privacy: 'private' }, + members: 1, + }); + + await groupLeader.update({'stats.mp': 200, 'stats.class': 'wizard', 'stats.lvl': 13}); + await groupLeader.post('/user/class/cast/earth', {quantity: 2}); + + await sleep(1); + await group.sync(); + + expect(group.chat[0]).to.exist; + expect(group.chat[0].uuid).to.equal('system'); + }); + it('searing brightness does not affect challenge or group tasks', async () => { let guild = await generateGroup(user); let challenge = await generateChallenge(user, guild); diff --git a/website/server/controllers/api-v3/user.js b/website/server/controllers/api-v3/user.js index 70721cfa54..5776c9ef73 100644 --- a/website/server/controllers/api-v3/user.js +++ b/website/server/controllers/api-v3/user.js @@ -1,16 +1,14 @@ import { authWithHeaders } from '../../middlewares/auth'; import common from '../../../common'; import { - NotFound, BadRequest, NotAuthorized, } from '../../libs/errors'; -import * as Tasks from '../../models/task'; import { basicFields as basicGroupFields, model as Group, } from '../../models/group'; -import { model as User } from '../../models/user'; +import * as Tasks from '../../models/task'; import Bluebird from 'bluebird'; import _ from 'lodash'; import * as passwordUtils from '../../libs/password'; @@ -524,224 +522,6 @@ api.getUserAnonymized = { }, }; -const partyMembersFields = 'profile.name stats achievements items.special'; - -async function castTaskSpell (res, req, targetId, user, spell) { - if (!targetId) throw new BadRequest(res.t('targetIdUUID')); - - const task = await Tasks.Task.findOne({ - _id: targetId, - userId: user._id, - }).exec(); - if (!task) throw new NotFound(res.t('taskNotFound')); - if (task.challenge.id) throw new BadRequest(res.t('challengeTasksNoCast')); - if (task.group.id) throw new BadRequest(res.t('groupTasksNoCast')); - - spell.cast(user, task, req); - - const results = await Bluebird.all([ - user.save(), - task.save(), - ]); - - return results; -} - -async function castMultiTaskSpell (req, user, spell) { - const tasks = await Tasks.Task.find({ - userId: user._id, - ...Tasks.taskIsGroupOrChallengeQuery, - }).exec(); - - spell.cast(user, tasks, req); - - const toSave = tasks - .filter(t => t.isModified()) - .map(t => t.save()); - toSave.unshift(user.save()); - const saved = await Bluebird.all(toSave); - - const response = { - tasks: saved, - user, - }; - - return response; -} - -async function castSelfSpell (req, user, spell) { - spell.cast(user, null, req); - await user.save(); -} - -async function castPartySpell (req, party, partyMembers, user, spell) { - if (!party) { - partyMembers = [user]; // Act as solo party - } else { - partyMembers = await User - .find({ - 'party._id': party._id, - _id: { $ne: user._id }, // add separately - }) - // .select(partyMembersFields) Selecting the entire user because otherwise when saving it'll save - // default values for non-selected fields and pre('save') will mess up thinking some values are missing - .exec(); - - partyMembers.unshift(user); - } - - spell.cast(user, partyMembers, req); - await Bluebird.all(partyMembers.map(m => m.save())); - - return partyMembers; -} - -async function castUserSpell (res, req, party, partyMembers, targetId, user, spell) { - if (!party && (!targetId || user._id === targetId)) { - partyMembers = user; - } else { - if (!targetId) throw new BadRequest(res.t('targetIdUUID')); - if (!party) throw new NotFound(res.t('partyNotFound')); - partyMembers = await User - .findOne({_id: targetId, 'party._id': party._id}) - // .select(partyMembersFields) Selecting the entire user because otherwise when saving it'll save - // default values for non-selected fields and pre('save') will mess up thinking some values are missing - .exec(); - } - - if (!partyMembers) throw new NotFound(res.t('userWithIDNotFound', {userId: targetId})); - - spell.cast(user, partyMembers, req); - - if (partyMembers !== user) { - await Bluebird.all([ - user.save(), - partyMembers.save(), - ]); - } else { - await partyMembers.save(); // partyMembers is user - } - - return partyMembers; -} - -/** - * @api {post} /api/v3/user/class/cast/:spellId Cast a skill (spell) on a target - * @apiName UserCast - * @apiGroup User - * - - * @apiParam (Path) {String=fireball, mpheal, earth, frost, smash, defensiveStance, valorousPresence, intimidate, pickPocket, backStab, toolsOfTrade, stealth, heal, protectAura, brightness, healAll} spellId The skill to cast. - * @apiParam (Query) {UUID} targetId Query parameter, necessary if the spell is cast on a party member or task. Not used if the spell is case on the user or the user's current party. - * @apiParamExample {json} Query example: - * Cast "Pickpocket" on a task: - * https://habitica.com/api/v3/user/class/cast/pickPocket?targetId=fd427623... - * - * Cast "Tools of the Trade" on the party: - * https://habitica.com/api/v3/user/class/cast/toolsOfTrade - * - * @apiSuccess data Will return the modified targets. For party members only the necessary fields will be populated. The user is always returned. - * - * @apiDescription Skill Key to Name Mapping - * Mage - * fireball: "Burst of Flames" - * mpheal: "Ethereal Surge" - * earth: "Earthquake" - * frost: "Chilling Frost" - * - * Warrior - * smash: "Brutal Smash" - * defensiveStance: "Defensive Stance" - * valorousPresence: "Valorous Presence" - * intimidate: "Intimidating Gaze" - * - * Rogue - * pickPocket: "Pickpocket" - * backStab: "Backstab" - * toolsOfTrade: "Tools of the Trade" - * stealth: "Stealth" - * - * Healer - * heal: "Healing Light" - * protectAura: "Protective Aura" - * brightness: "Searing Brightness" - * healAll: "Blessing" - * - * @apiError (400) {NotAuthorized} Not enough mana. - * @apiUse TaskNotFound - * @apiUse PartyNotFound - * @apiUse UserNotFound - */ -api.castSpell = { - method: 'POST', - middlewares: [authWithHeaders()], - url: '/user/class/cast/:spellId', - async handler (req, res) { - let user = res.locals.user; - let spellId = req.params.spellId; - let targetId = req.query.targetId; - - // optional because not required by all targetTypes, presence is checked later if necessary - req.checkQuery('targetId', res.t('targetIdUUID')).optional().isUUID(); - - let reqValidationErrors = req.validationErrors(); - if (reqValidationErrors) throw reqValidationErrors; - - let klass = common.content.spells.special[spellId] ? 'special' : user.stats.class; - let spell = common.content.spells[klass][spellId]; - - if (!spell) throw new NotFound(res.t('spellNotFound', {spellId})); - if (spell.mana > user.stats.mp) throw new NotAuthorized(res.t('notEnoughMana')); - if (spell.value > user.stats.gp && !spell.previousPurchase) throw new NotAuthorized(res.t('messageNotEnoughGold')); - if (spell.lvl > user.stats.lvl) throw new NotAuthorized(res.t('spellLevelTooHigh', {level: spell.lvl})); - - let targetType = spell.target; - - if (targetType === 'task') { - const results = await castTaskSpell(res, req, targetId, user, spell); - res.respond(200, { - user: results[0], - task: results[1], - }); - } else if (targetType === 'self') { - await castSelfSpell(req, user, spell); - res.respond(200, { user }); - } else if (targetType === 'tasks') { // new target type in v3: when all the user's tasks are necessary - const response = await castMultiTaskSpell(req, user, spell); - res.respond(200, response); - } else if (targetType === 'party' || targetType === 'user') { - const party = await Group.getGroup({groupId: 'party', user}); - // arrays of users when targetType is 'party' otherwise single users - let partyMembers; - - if (targetType === 'party') { - partyMembers = await castPartySpell(req, party, partyMembers, user, spell); - } else { - partyMembers = await castUserSpell(res, req, party, partyMembers, targetId, user, spell); - } - - let partyMembersRes = Array.isArray(partyMembers) ? partyMembers : [partyMembers]; - - // Only return some fields. - // See comment above on why we can't just select the necessary fields when querying - partyMembersRes = partyMembersRes.map(partyMember => { - return common.pickDeep(partyMember.toJSON(), common.$w(partyMembersFields)); - }); - - res.respond(200, { - partyMembers: partyMembersRes, - user, - }); - - if (party && !spell.silent) { - let message = `\`${user.profile.name} casts ${spell.text()}${targetType === 'user' ? ` on ${partyMembers.profile.name}` : ' for the party'}.\``; - party.sendChat(message); - await party.save(); - } - } - }, -}; - /** * @api {post} /api/v3/user/sleep Make the user start / stop sleeping (resting in the Inn) * @apiName UserSleep diff --git a/website/server/controllers/api-v3/user/spells.js b/website/server/controllers/api-v3/user/spells.js new file mode 100644 index 0000000000..35415137eb --- /dev/null +++ b/website/server/controllers/api-v3/user/spells.js @@ -0,0 +1,140 @@ +import { authWithHeaders } from '../../../middlewares/auth'; +import common from '../../../../common'; +import { + model as Group, +} from '../../../models/group'; +import { + NotAuthorized, + NotFound, +} from '../../../libs/errors'; +import { + castTaskSpell, + castMultiTaskSpell, + castSelfSpell, + castPartySpell, + castUserSpell, +} from '../../../libs/spells'; + +const partyMembersFields = 'profile.name stats achievements items.special'; + +let api = {}; + +/** + * @api {post} /api/v3/user/class/cast/:spellId Cast a skill (spell) on a target + * @apiName UserCast + * @apiGroup User + * + + * @apiParam (Path) {String=fireball, mpheal, earth, frost, smash, defensiveStance, valorousPresence, intimidate, pickPocket, backStab, toolsOfTrade, stealth, heal, protectAura, brightness, healAll} spellId The skill to cast. + * @apiParam (Query) {UUID} targetId Query parameter, necessary if the spell is cast on a party member or task. Not used if the spell is case on the user or the user's current party. + * @apiParamExample {json} Query example: + * Cast "Pickpocket" on a task: + * https://habitica.com/api/v3/user/class/cast/pickPocket?targetId=fd427623... + * + * Cast "Tools of the Trade" on the party: + * https://habitica.com/api/v3/user/class/cast/toolsOfTrade + * + * @apiSuccess data Will return the modified targets. For party members only the necessary fields will be populated. The user is always returned. + * + * @apiDescription Skill Key to Name Mapping + * Mage + * fireball: "Burst of Flames" + * mpheal: "Ethereal Surge" + * earth: "Earthquake" + * frost: "Chilling Frost" + * + * Warrior + * smash: "Brutal Smash" + * defensiveStance: "Defensive Stance" + * valorousPresence: "Valorous Presence" + * intimidate: "Intimidating Gaze" + * + * Rogue + * pickPocket: "Pickpocket" + * backStab: "Backstab" + * toolsOfTrade: "Tools of the Trade" + * stealth: "Stealth" + * + * Healer + * heal: "Healing Light" + * protectAura: "Protective Aura" + * brightness: "Searing Brightness" + * healAll: "Blessing" + * + * @apiError (400) {NotAuthorized} Not enough mana. + * @apiUse TaskNotFound + * @apiUse PartyNotFound + * @apiUse UserNotFound + */ +api.castSpell = { + method: 'POST', + middlewares: [authWithHeaders()], + url: '/user/class/cast/:spellId', + async handler (req, res) { + let user = res.locals.user; + let spellId = req.params.spellId; + let targetId = req.query.targetId; + const quantity = req.body.quantity || 1; + + // optional because not required by all targetTypes, presence is checked later if necessary + req.checkQuery('targetId', res.t('targetIdUUID')).optional().isUUID(); + + let reqValidationErrors = req.validationErrors(); + if (reqValidationErrors) throw reqValidationErrors; + + let klass = common.content.spells.special[spellId] ? 'special' : user.stats.class; + let spell = common.content.spells[klass][spellId]; + + if (!spell) throw new NotFound(res.t('spellNotFound', {spellId})); + if (spell.mana > user.stats.mp) throw new NotAuthorized(res.t('notEnoughMana')); + if (spell.value > user.stats.gp && !spell.previousPurchase) throw new NotAuthorized(res.t('messageNotEnoughGold')); + if (spell.lvl > user.stats.lvl) throw new NotAuthorized(res.t('spellLevelTooHigh', {level: spell.lvl})); + + let targetType = spell.target; + + if (targetType === 'task') { + const results = await castTaskSpell(res, req, targetId, user, spell, quantity); + res.respond(200, { + user: results[0], + task: results[1], + }); + } else if (targetType === 'self') { + await castSelfSpell(req, user, spell, quantity); + res.respond(200, { user }); + } else if (targetType === 'tasks') { // new target type in v3: when all the user's tasks are necessary + const response = await castMultiTaskSpell(req, user, spell, quantity); + res.respond(200, response); + } else if (targetType === 'party' || targetType === 'user') { + const party = await Group.getGroup({groupId: 'party', user}); + // arrays of users when targetType is 'party' otherwise single users + let partyMembers; + + if (targetType === 'party') { + partyMembers = await castPartySpell(req, party, partyMembers, user, spell, quantity); + } else { + partyMembers = await castUserSpell(res, req, party, partyMembers, targetId, user, spell, quantity); + } + + let partyMembersRes = Array.isArray(partyMembers) ? partyMembers : [partyMembers]; + + // Only return some fields. + // See comment above on why we can't just select the necessary fields when querying + partyMembersRes = partyMembersRes.map(partyMember => { + return common.pickDeep(partyMember.toJSON(), common.$w(partyMembersFields)); + }); + + res.respond(200, { + partyMembers: partyMembersRes, + user, + }); + + if (party && !spell.silent) { + let message = `\`${user.profile.name} casts ${spell.text()}${targetType === 'user' ? ` on ${partyMembers.profile.name}` : ' for the party'}.\``; + party.sendChat(message); + await party.save(); + } + } + }, +}; + +module.exports = api; diff --git a/website/server/libs/spells.js b/website/server/libs/spells.js new file mode 100644 index 0000000000..ba728e3664 --- /dev/null +++ b/website/server/libs/spells.js @@ -0,0 +1,121 @@ +import Bluebird from 'bluebird'; + +import { model as User } from '../models/user'; +import * as Tasks from '../models/task'; +import { + NotFound, + BadRequest, +} from './errors'; + +// @TODO: After refactoring individual spells, move quantity to the calculations + +async function castTaskSpell (res, req, targetId, user, spell, quantity = 1) { + if (!targetId) throw new BadRequest(res.t('targetIdUUID')); + + const task = await Tasks.Task.findOne({ + _id: targetId, + userId: user._id, + }).exec(); + if (!task) throw new NotFound(res.t('taskNotFound')); + if (task.challenge.id) throw new BadRequest(res.t('challengeTasksNoCast')); + if (task.group.id) throw new BadRequest(res.t('groupTasksNoCast')); + + for (let i = 0; i < quantity; i += 1) { + spell.cast(user, task, req); + } + + const results = await Bluebird.all([ + user.save(), + task.save(), + ]); + + return results; +} + +async function castMultiTaskSpell (req, user, spell, quantity = 1) { + const tasks = await Tasks.Task.find({ + userId: user._id, + ...Tasks.taskIsGroupOrChallengeQuery, + }).exec(); + + for (let i = 0; i < quantity; i += 1) { + spell.cast(user, tasks, req); + } + + const toSave = tasks + .filter(t => t.isModified()) + .map(t => t.save()); + toSave.unshift(user.save()); + const saved = await Bluebird.all(toSave); + + const response = { + tasks: saved, + user, + }; + + return response; +} + +async function castSelfSpell (req, user, spell, quantity = 1) { + for (let i = 0; i < quantity; i += 1) { + spell.cast(user, null, req); + } + await user.save(); +} + +async function castPartySpell (req, party, partyMembers, user, spell, quantity = 1) { + if (!party) { + partyMembers = [user]; // Act as solo party + } else { + partyMembers = await User + .find({ + 'party._id': party._id, + _id: { $ne: user._id }, // add separately + }) + // .select(partyMembersFields) Selecting the entire user because otherwise when saving it'll save + // default values for non-selected fields and pre('save') will mess up thinking some values are missing + .exec(); + + partyMembers.unshift(user); + } + + for (let i = 0; i < quantity; i += 1) { + spell.cast(user, partyMembers, req); + } + await Bluebird.all(partyMembers.map(m => m.save())); + + return partyMembers; +} + +async function castUserSpell (res, req, party, partyMembers, targetId, user, spell, quantity = 1) { + if (!party && (!targetId || user._id === targetId)) { + partyMembers = user; + } else { + if (!targetId) throw new BadRequest(res.t('targetIdUUID')); + if (!party) throw new NotFound(res.t('partyNotFound')); + partyMembers = await User + .findOne({_id: targetId, 'party._id': party._id}) + // .select(partyMembersFields) Selecting the entire user because otherwise when saving it'll save + // default values for non-selected fields and pre('save') will mess up thinking some values are missing + .exec(); + } + + if (!partyMembers) throw new NotFound(res.t('userWithIDNotFound', {userId: targetId})); + + for (let i = 0; i < quantity; i += 1) { + spell.cast(user, partyMembers, req); + } + + if (partyMembers !== user) { + await Bluebird.all([ + user.save(), + partyMembers.save(), + ]); + } else { + await partyMembers.save(); // partyMembers is user + } + + return partyMembers; +} + +export {castTaskSpell, castMultiTaskSpell, castSelfSpell, castPartySpell, castUserSpell}; From 4b17f62241901ce8575b1299d922c9ebb25b5638 Mon Sep 17 00:00:00 2001 From: Gergely Imreh Date: Fri, 23 Feb 2018 20:38:18 +0000 Subject: [PATCH 04/15] task types: add new error messages for invalid task types (#9983) * `/task/user` `GET` endpoint takes plurals of task types as allowed parameters, plus another custom `completedTodos` type * `/tasks/group/:groupId` and `/tasks/challenge/:challengeId` `GET` endpoints task plurals of task types as allowed parameters --- website/common/locales/en/tasks.json | 2 ++ website/server/controllers/api-v3/tasks.js | 4 ++-- website/server/controllers/api-v3/tasks/groups.js | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/website/common/locales/en/tasks.json b/website/common/locales/en/tasks.json index 0e7b7fda6e..df45b1b9aa 100644 --- a/website/common/locales/en/tasks.json +++ b/website/common/locales/en/tasks.json @@ -149,6 +149,8 @@ "taskAliasAlreadyUsed": "Task alias already used on another task.", "taskNotFound": "Task not found.", "invalidTaskType": "Task type must be one of \"habit\", \"daily\", \"todo\", \"reward\".", + "invalidTasksType": "Task type must be one of \"habits\", \"dailys\", \"todos\", \"rewards\".", + "invalidTasksTypeExtra": "Task type must be one of \"habits\", \"dailys\", \"todos\", \"rewards\", \"completedTodos\".", "cantDeleteChallengeTasks": "A task belonging to a challenge can't be deleted.", "checklistOnlyDailyTodo": "Checklists are supported only on Dailies and To-Dos", "checklistItemNotFound": "No checklist item was found with given id.", diff --git a/website/server/controllers/api-v3/tasks.js b/website/server/controllers/api-v3/tasks.js index 0e46bb10e7..8b9672afd0 100644 --- a/website/server/controllers/api-v3/tasks.js +++ b/website/server/controllers/api-v3/tasks.js @@ -287,7 +287,7 @@ api.getUserTasks = { async handler (req, res) { let types = Tasks.tasksTypes.map(type => `${type}s`); types.push('completedTodos', '_allCompletedTodos'); // _allCompletedTodos is currently in BETA and is likely to be removed in future - req.checkQuery('type', res.t('invalidTaskType')).optional().isIn(types); + req.checkQuery('type', res.t('invalidTasksTypeExtra')).optional().isIn(types); let validationErrors = req.validationErrors(); if (validationErrors) throw validationErrors; @@ -325,7 +325,7 @@ api.getChallengeTasks = { async handler (req, res) { req.checkParams('challengeId', res.t('challengeIdRequired')).notEmpty().isUUID(); let types = Tasks.tasksTypes.map(type => `${type}s`); - req.checkQuery('type', res.t('invalidTaskType')).optional().isIn(types); + req.checkQuery('type', res.t('invalidTasksType')).optional().isIn(types); let validationErrors = req.validationErrors(); if (validationErrors) throw validationErrors; diff --git a/website/server/controllers/api-v3/tasks/groups.js b/website/server/controllers/api-v3/tasks/groups.js index 62ebb403ee..449a0e39b0 100644 --- a/website/server/controllers/api-v3/tasks/groups.js +++ b/website/server/controllers/api-v3/tasks/groups.js @@ -86,7 +86,7 @@ api.getGroupTasks = { middlewares: [authWithHeaders()], async handler (req, res) { req.checkParams('groupId', res.t('groupIdRequired')).notEmpty().isUUID(); - req.checkQuery('type', res.t('invalidTaskType')).optional().isIn(types); + req.checkQuery('type', res.t('invalidTasksType')).optional().isIn(types); let validationErrors = req.validationErrors(); if (validationErrors) throw validationErrors; From 67961be5759fa51fa420903b3932a76789b15ab9 Mon Sep 17 00:00:00 2001 From: Matteo Pagliazzi Date: Sat, 24 Feb 2018 12:05:13 +0100 Subject: [PATCH 05/15] greenkeeper: exclude mongoose --- package.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/package.json b/package.json index b435d3fa9c..4301dc8962 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,11 @@ "description": "A habit tracker app which treats your goals like a Role Playing Game.", "version": "4.28.0", "main": "./website/server/index.js", + "greenkeeper": { + "ignore": [ + "mongoose" + ] + }, "dependencies": { "@slack/client": "^3.8.1", "accepts": "^1.3.2", From b1bd24389229c31b6d51305cda2bf9eb7c92aa62 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Sat, 24 Feb 2018 12:46:42 +0100 Subject: [PATCH 06/15] docs(readme): add Greenkeeper badge (#10050) --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 07d59ebcbc..22b1589b9b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ Habitica [![Build Status](https://travis-ci.org/HabitRPG/habitica.svg?branch=develop)](https://travis-ci.org/HabitRPG/habitica) [![Code Climate](https://codeclimate.com/github/HabitRPG/habitrpg.svg)](https://codeclimate.com/github/HabitRPG/habitrpg) [![Coverage Status](https://coveralls.io/repos/github/HabitRPG/habitica/badge.svg?branch=develop)](https://coveralls.io/github/HabitRPG/habitica?branch=develop) [![Bountysource](https://api.bountysource.com/badge/tracker?tracker_id=68393)](https://www.bountysource.com/trackers/68393-habitrpg?utm_source=68393&utm_medium=shield&utm_campaign=TRACKER_BADGE) [![Open Source Helpers](https://www.codetriage.com/habitrpg/habitica/badges/users.svg)](https://www.codetriage.com/habitrpg/habitica) =============== +[![Greenkeeper badge](https://badges.greenkeeper.io/HabitRPG/habitica.svg)](https://greenkeeper.io/) + [Habitica](https://habitica.com) is an open source habit building program which treats your life like a Role Playing Game. Level up as you succeed, lose HP as you fail, earn money to buy weapons and armor. We need more programmers! Your assistance will be greatly appreciated. From 84dc2f8e3c683d529292a6464df397aab311cd44 Mon Sep 17 00:00:00 2001 From: Keith Holliday Date: Sun, 25 Feb 2018 08:04:55 -0700 Subject: [PATCH 07/15] Fixed sorting and model creation for inboxes (#10039) --- website/client/components/userMenu/inbox.vue | 65 +++++++++++--------- 1 file changed, 37 insertions(+), 28 deletions(-) diff --git a/website/client/components/userMenu/inbox.vue b/website/client/components/userMenu/inbox.vue index df63939f05..c7372402e2 100644 --- a/website/client/components/userMenu/inbox.vue +++ b/website/client/components/userMenu/inbox.vue @@ -160,6 +160,7 @@ import Vue from 'vue'; import moment from 'moment'; import filter from 'lodash/filter'; import sortBy from 'lodash/sortBy'; +import groupBy from 'lodash/groupBy'; import { mapState } from 'client/libs/store'; import styleHelper from 'client/mixins/styleHelper'; @@ -220,43 +221,51 @@ export default { computed: { ...mapState({user: 'user.data'}), conversations () { - let conversations = {}; - for (let messageId in this.user.inbox.messages) { - let message = this.user.inbox.messages[messageId]; - let userId = message.uuid; + const inboxGroup = groupBy(this.user.inbox.messages, 'uuid'); - if (!conversations[userId]) { - conversations[userId] = { - name: message.user, - key: userId, - messages: [], - }; + // Create conversation objects + const convos = []; + for (let key in inboxGroup) { + const convoSorted = sortBy(inboxGroup[key], [(o) => { + return o.timestamp; + }]); + + // Fix poor inbox chat models + const newChatModels = convoSorted.map(chat => { + let newChat = Object.assign({}, chat); + if (newChat.sent) { + newChat.toUUID = newChat.uuid; + newChat.toUser = newChat.user; + newChat.uuid = this.user._id; + newChat.user = this.user.profile.name; + newChat.contributor = this.user.contributor; + newChat.backer = this.user.backer; + } + return newChat; + }); + + const recentMessage = newChatModels[newChatModels.length - 1]; + + // Special case where we have placeholder message because conversations are just grouped messages for now + if (!recentMessage.text) { + newChatModels.splice(newChatModels.length - 1, 1); } - let newMessage = { - text: message.text, - timestamp: message.timestamp, - user: message.user, - uuid: message.uuid, - id: message.id, - contributor: message.contributor, + const convoModel = { + name: recentMessage.toUser ? recentMessage.toUser : recentMessage.user, // Handles case where from user sent the only message or the to user sent the only message + key: recentMessage.toUUID ? recentMessage.toUUID : recentMessage.uuid, + messages: newChatModels, + lastMessageText: recentMessage.text, + date: recentMessage.timestamp, }; - if (message.sent) { - newMessage.user = this.user.profile.name; - newMessage.uuid = this.user._id; - newMessage.contributor = this.user.contributor; - } - - if (newMessage.text) conversations[userId].messages.push(newMessage); - conversations[userId].lastMessageText = message.text; - conversations[userId].date = message.timestamp; + convos.push(convoModel); } - conversations = sortBy(conversations, [(o) => { + // Sort models by most recent + const conversations = sortBy(convos, [(o) => { return moment(o.date).toDate(); }]); - conversations = conversations.reverse(); return conversations; }, From 01dab33b9645e20f5083d6ab7836bd476119c993 Mon Sep 17 00:00:00 2001 From: Alys Date: Mon, 26 Feb 2018 19:40:19 +1000 Subject: [PATCH 08/15] adjust loading screen tip to refer to Tags button by its correct name (not Filters) --- website/common/locales/en/loadingScreenTips.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/common/locales/en/loadingScreenTips.json b/website/common/locales/en/loadingScreenTips.json index ec50ad2ee1..cbbffb7501 100644 --- a/website/common/locales/en/loadingScreenTips.json +++ b/website/common/locales/en/loadingScreenTips.json @@ -12,7 +12,7 @@ "tip10": "You can win gems by competing in Challenges. New ones are added every day!", "tip11": "Having more than four Party members increases accountability!", "tip12": "Add checklists to your To-Dos to multiply your rewards!", - "tip13": "Click “Filters” on your task page to make an unwieldy task list very manageable!", + "tip13": "Click “Tags” on your task page to make an unwieldy task list very manageable!", "tip14": "You can add headers or inspirational quotes to your list as Habits with no (+/-).", "tip15": "Complete all the Masterclasser Quest-lines to learn about Habitica’s secret lore.", "tip16": "Click the link to the Data Display Tool in the footer for valuable insights on your progress.", From 6118336a5dd593053eb2d8d878ae67d171ed17ca Mon Sep 17 00:00:00 2001 From: Keith Holliday Date: Mon, 26 Feb 2018 12:07:54 -0700 Subject: [PATCH 09/15] Marked new stuff complete when clicking later (#10043) * Marked new stuff complete when clicking later * Moved flag set to action --- website/client/store/actions/user.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/website/client/store/actions/user.js b/website/client/store/actions/user.js index 35402430f9..ecbd5683a5 100644 --- a/website/client/store/actions/user.js +++ b/website/client/store/actions/user.js @@ -119,7 +119,8 @@ export function openMysteryItem () { return axios.post('/api/v3/user/open-mystery-item'); } -export function newStuffLater () { +export function newStuffLater (store) { + store.state.user.data.flags.newStuff = false; return axios.post('/api/v3/news/tell-me-later'); } From 7dcd550209e765d0a11550da19f515f26c1656e6 Mon Sep 17 00:00:00 2001 From: Keith Holliday Date: Mon, 26 Feb 2018 13:03:04 -0700 Subject: [PATCH 10/15] Updated membercount checks (#10006) * Updated membercount checks * Added get member count method * Updated tests to correctly add users --- .../v3/unit/libs/payments/amazon/subscribe.test.js | 12 ++++++++++-- .../payments/stripe/checkout-subscription.test.js | 11 +++++++++++ website/server/libs/amazonPayments.js | 8 ++++---- website/server/libs/stripePayments.js | 7 ++++--- website/server/models/group.js | 10 ++++++++++ 5 files changed, 39 insertions(+), 9 deletions(-) diff --git a/test/api/v3/unit/libs/payments/amazon/subscribe.test.js b/test/api/v3/unit/libs/payments/amazon/subscribe.test.js index 930c64d06a..b0ae072880 100644 --- a/test/api/v3/unit/libs/payments/amazon/subscribe.test.js +++ b/test/api/v3/unit/libs/payments/amazon/subscribe.test.js @@ -216,6 +216,9 @@ describe('Amazon Payments - Subscribe', () => { }); it('subscribes with amazon', async () => { + user.guilds.push(groupId); + await user.save(); + await amzLib.subscribe({ billingAgreementId, sub, @@ -241,8 +244,13 @@ describe('Amazon Payments - Subscribe', () => { user = new User(); user.guilds.push(groupId); await user.save(); - group.memberCount = 2; - await group.save(); + + // Add existing users + user = new User(); + user.guilds.push(groupId); + await user.save(); + + // Set expected amount sub.key = 'group_monthly'; sub.price = 9; amount = 12; diff --git a/test/api/v3/unit/libs/payments/stripe/checkout-subscription.test.js b/test/api/v3/unit/libs/payments/stripe/checkout-subscription.test.js index 7271b1dbd3..411acbedaf 100644 --- a/test/api/v3/unit/libs/payments/stripe/checkout-subscription.test.js +++ b/test/api/v3/unit/libs/payments/stripe/checkout-subscription.test.js @@ -227,6 +227,11 @@ describe('checkout with subscription', () => { sub = data.sub; groupId = group._id; email = 'test@test.com'; + + // Add user to group + user.guilds.push(groupId); + await user.save(); + headers = {}; await stripePayments.checkout({ @@ -267,9 +272,15 @@ describe('checkout with subscription', () => { groupId = group._id; email = 'test@test.com'; headers = {}; + + // Add user to group + user.guilds.push(groupId); + await user.save(); + user = new User(); user.guilds.push(groupId); await user.save(); + group.memberCount = 2; await group.save(); diff --git a/website/server/libs/amazonPayments.js b/website/server/libs/amazonPayments.js index f71185526f..75ccddcecc 100644 --- a/website/server/libs/amazonPayments.js +++ b/website/server/libs/amazonPayments.js @@ -270,10 +270,10 @@ api.subscribe = async function subscribe (options) { let priceOfSingleMember = 3; if (groupId) { - let groupFields = basicGroupFields.concat(' purchased'); - let group = await Group.getGroup({user, groupId, populateLeader: false, groupFields}); - - amount = sub.price + (group.memberCount - leaderCount) * priceOfSingleMember; + const groupFields = basicGroupFields.concat(' purchased'); + const group = await Group.getGroup({user, groupId, populateLeader: false, groupFields}); + const membersCount = await group.getMemberCount(); + amount = sub.price + (membersCount - leaderCount) * priceOfSingleMember; } await this.setBillingAgreementDetails({ diff --git a/website/server/libs/stripePayments.js b/website/server/libs/stripePayments.js index 63b2041ca3..7cf6c0b75e 100644 --- a/website/server/libs/stripePayments.js +++ b/website/server/libs/stripePayments.js @@ -97,9 +97,10 @@ api.checkout = async function checkout (options, stripeInc) { if (groupId) { customerObject.quantity = sub.quantity; - let groupFields = basicGroupFields.concat(' purchased'); - let group = await Group.getGroup({user, groupId, populateLeader: false, groupFields}); - customerObject.quantity = group.memberCount + sub.quantity - 1; + const groupFields = basicGroupFields.concat(' purchased'); + const group = await Group.getGroup({user, groupId, populateLeader: false, groupFields}); + const membersCount = await group.getMemberCount(); + customerObject.quantity = membersCount + sub.quantity - 1; } response = await stripeApi.customers.create(customerObject); diff --git a/website/server/models/group.js b/website/server/models/group.js index 45c7c99235..3a0e8df2c5 100644 --- a/website/server/models/group.js +++ b/website/server/models/group.js @@ -442,6 +442,16 @@ schema.methods.isMember = function isGroupMember (user) { } }; +schema.methods.getMemberCount = async function getMemberCount () { + let query = { guilds: this._id }; + + if (this.type === 'party') { + query = { 'party._id': this._id }; + } + + return await User.count(query).exec(); +}; + export function chatDefaults (msg, user) { let message = { id: shared.uuid(), From be71c5f84473df86ca234893614e34a94ab2c12f Mon Sep 17 00:00:00 2001 From: Keith Holliday Date: Tue, 27 Feb 2018 09:57:37 -0700 Subject: [PATCH 11/15] Added kafka queue and initial messages for delete account (#10036) * Added kafka queue and initial messages for delete account * Checked for env vars --- config.json.example | 7 ++++ package-lock.json | 14 ++++++++ package.json | 1 + website/server/controllers/api-v3/user.js | 3 ++ website/server/libs/queue/index.js | 43 +++++++++++++++++++++++ 5 files changed, 68 insertions(+) create mode 100644 website/server/libs/queue/index.js diff --git a/config.json.example b/config.json.example index 1aa62936b7..efaa9298f6 100644 --- a/config.json.example +++ b/config.json.example @@ -105,5 +105,12 @@ "LOGGLY" : { "TOKEN" : "example-token", "SUBDOMAIN" : "exmaple-subdomain" + }, + "KAFKA": { + "GROUP_ID": "", + "CLOUDKARAFKA_BROKERS": "", + "CLOUDKARAFKA_USERNAME": "", + "CLOUDKARAFKA_PASSWORD": "", + "CLOUDKARAFKA_TOPIC_PREFIX": "" } } diff --git a/package-lock.json b/package-lock.json index 569840dc8a..34fba602b5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1945,6 +1945,11 @@ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.11.0.tgz", "integrity": "sha1-RqoXUftqL5PuXmibsQh9SxTGwgU=" }, + "bindings": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.3.0.tgz", + "integrity": "sha512-DpLh5EzMR2kzvX1KIlVC0VkC3iZtHKTgdtZ0a3pglBZdaQFjt5S9g9xd1lE+YvXyfd6mtCeRnrUfOLYiTMlNSw==" + }, "bitsyntax": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/bitsyntax/-/bitsyntax-0.0.4.tgz", @@ -13888,6 +13893,15 @@ } } }, + "node-rdkafka": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/node-rdkafka/-/node-rdkafka-2.2.3.tgz", + "integrity": "sha1-BdjK/brye/Ho7yuOW/Oa+1mi5wE=", + "requires": { + "bindings": "1.3.0", + "nan": "2.6.2" + } + }, "node-sass": { "version": "4.7.2", "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.7.2.tgz", diff --git a/package.json b/package.json index 4301dc8962..bed67e00ce 100644 --- a/package.json +++ b/package.json @@ -71,6 +71,7 @@ "morgan": "^1.7.0", "nconf": "^0.10.0", "node-gcm": "^0.14.4", + "node-rdkafka": "^2.2.3", "node-sass": "^4.5.0", "nodemailer": "^4.5.0", "ora": "^2.0.0", diff --git a/website/server/controllers/api-v3/user.js b/website/server/controllers/api-v3/user.js index 5776c9ef73..ac2e82722d 100644 --- a/website/server/controllers/api-v3/user.js +++ b/website/server/controllers/api-v3/user.js @@ -16,6 +16,7 @@ import { getUserInfo, sendTxn as txnEmail, } from '../../libs/email'; +import Queue from '../../libs/queue'; import nconf from 'nconf'; import get from 'lodash/get'; @@ -432,6 +433,8 @@ api.deleteUser = { ]); } + if (feedback) Queue.sendMessage({feedback, username: user.profile.name}, user._id); + res.analytics.track('account delete', { uuid: user._id, hitType: 'event', diff --git a/website/server/libs/queue/index.js b/website/server/libs/queue/index.js new file mode 100644 index 0000000000..90f7cfe1e8 --- /dev/null +++ b/website/server/libs/queue/index.js @@ -0,0 +1,43 @@ +import Kafka from 'node-rdkafka'; +import nconf from 'nconf'; + +const GROUP_ID = nconf.get('KAFKA:GROUP_ID'); +const CLOUDKARAFKA_BROKERS = nconf.get('KAFKA:CLOUDKARAFKA_BROKERS'); +const CLOUDKARAFKA_USERNAME = nconf.get('KAFKA:CLOUDKARAFKA_USERNAME'); +const CLOUDKARAFKA_PASSWORD = nconf.get('KAFKA:CLOUDKARAFKA_PASSWORD'); +const CLOUDKARAFKA_TOPIC_PREFIX = nconf.get('KAFKA:CLOUDKARAFKA_TOPIC_PREFIX'); + +const kafkaConf = { + 'group.id': GROUP_ID, + 'metadata.broker.list': CLOUDKARAFKA_BROKERS ? CLOUDKARAFKA_BROKERS.split(',') : '', + 'socket.keepalive.enable': true, + 'security.protocol': 'SASL_SSL', + 'sasl.mechanisms': 'SCRAM-SHA-256', + 'sasl.username': CLOUDKARAFKA_USERNAME, + 'sasl.password': CLOUDKARAFKA_PASSWORD, + debug: 'generic,broker,security', +}; + +const prefix = CLOUDKARAFKA_TOPIC_PREFIX; +const topic = `${prefix}-default`; +const producer = new Kafka.Producer(kafkaConf); + +producer.connect(); + +process.on('exit', () => { + if (producer.isConnected()) producer.disconnect(); +}); + +const api = {}; + +api.sendMessage = function sendMessage (message, key) { + if (!producer.isConnected()) return; + + try { + producer.produce(topic, -1, new Buffer(JSON.stringify(message)), key); + } catch (e) { + // @TODO: Send the to loggly? + } +}; + +module.exports = api; From 3e723ec4de986469fd468978af8053ac4fb3d62d Mon Sep 17 00:00:00 2001 From: Matteo Pagliazzi Date: Tue, 27 Feb 2018 22:02:12 +0100 Subject: [PATCH 12/15] fixes #9952 (#10062) --- test/api/v3/unit/models/user.test.js | 46 ++++++++++++++++++++++++++++ website/server/models/user/hooks.js | 5 +-- 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/test/api/v3/unit/models/user.test.js b/test/api/v3/unit/models/user.test.js index 63d99193b6..14959bd823 100644 --- a/test/api/v3/unit/models/user.test.js +++ b/test/api/v3/unit/models/user.test.js @@ -352,7 +352,12 @@ describe('User Model', () => { context('manage unallocated stats points notifications', () => { it('doesn\'t add a notification if there are no points to allocate', async () => { let user = new User(); + + user.flags.classSelected = true; + user.preferences.disableClasses = false; + user.stats.class = 'warrior'; user = await user.save(); // necessary for user.isSelected to work correctly + const oldNotificationsCount = user.notifications.length; user.stats.points = 0; @@ -363,6 +368,10 @@ describe('User Model', () => { it('removes a notification if there are no more points to allocate', async () => { let user = new User(); + + user.flags.classSelected = true; + user.preferences.disableClasses = false; + user.stats.class = 'warrior'; user.stats.points = 9; user = await user.save(); // necessary for user.isSelected to work correctly @@ -377,6 +386,9 @@ describe('User Model', () => { it('adds a notification if there are points to allocate', async () => { let user = new User(); + user.flags.classSelected = true; + user.preferences.disableClasses = false; + user.stats.class = 'warrior'; user = await user.save(); // necessary for user.isSelected to work correctly const oldNotificationsCount = user.notifications.length; @@ -391,6 +403,9 @@ describe('User Model', () => { it('adds a notification if the points to allocate have changed', async () => { let user = new User(); user.stats.points = 9; + user.flags.classSelected = true; + user.preferences.disableClasses = false; + user.stats.class = 'warrior'; user = await user.save(); // necessary for user.isSelected to work correctly const oldNotificationsCount = user.notifications.length; @@ -406,6 +421,37 @@ describe('User Model', () => { expect(user.notifications[0].data.points).to.equal(11); expect(user.notifications[0].id).to.not.equal(oldNotificationsUUID); }); + + it('does not add a notification if the user has disabled classes', async () => { + let user = new User(); + user.stats.points = 9; + user.flags.classSelected = true; + user.preferences.disableClasses = true; + user.stats.class = 'warrior'; + user = await user.save(); // necessary for user.isSelected to work correctly + + const oldNotificationsCount = user.notifications.length; + + user.stats.points = 9; + user = await user.save(); + + expect(user.notifications.length).to.equal(oldNotificationsCount); + }); + + it('does not add a notification if the user has not selected a class', async () => { + let user = new User(); + user.stats.points = 9; + user.flags.classSelected = false; + user.stats.class = 'warrior'; + user = await user.save(); // necessary for user.isSelected to work correctly + + const oldNotificationsCount = user.notifications.length; + + user.stats.points = 9; + user = await user.save(); + + expect(user.notifications.length).to.equal(oldNotificationsCount); + }); }); }); diff --git a/website/server/models/user/hooks.js b/website/server/models/user/hooks.js index 022137ef43..3667fb5860 100644 --- a/website/server/models/user/hooks.js +++ b/website/server/models/user/hooks.js @@ -253,8 +253,9 @@ schema.pre('save', true, function preSaveUser (next, done) { } // Manage unallocated stats points notifications - if (this.isSelected('stats') && this.isSelected('notifications')) { + if (this.isSelected('stats') && this.isSelected('notifications') && this.isSelected('flags') && this.isSelected('preferences')) { const pointsToAllocate = this.stats.points; + const classNotEnabled = !this.flags.classSelected || this.preferences.disableClasses; // Sometimes there can be more than 1 notification const existingNotifications = this.notifications.filter(notification => { @@ -271,7 +272,7 @@ schema.pre('save', true, function preSaveUser (next, done) { let notificationsToRemove = outdatedNotification ? existingNotificationsLength : existingNotificationsLength - 1; // If there are points to allocate and the notification is outdated, add a new notifications - if (pointsToAllocate > 0 && outdatedNotification) { + if (pointsToAllocate > 0 && !classNotEnabled && outdatedNotification) { this.addNotification('UNALLOCATED_STATS_POINTS', { points: pointsToAllocate }); } From 5e5d73816b3c8f124e04a348c50909baa26ce80c Mon Sep 17 00:00:00 2001 From: Matteo Pagliazzi Date: Tue, 27 Feb 2018 22:24:31 +0100 Subject: [PATCH 13/15] fixes #10057 (#10063) --- website/client/components/inventory/equipment/index.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/client/components/inventory/equipment/index.vue b/website/client/components/inventory/equipment/index.vue index f676f7ccf0..6caa7f3fcd 100644 --- a/website/client/components/inventory/equipment/index.vue +++ b/website/client/components/inventory/equipment/index.vue @@ -13,8 +13,8 @@ :key="group.key", ) .custom-control.custom-checkbox - input.custom-control-input(type="checkbox", v-model="viewOptions[group.key].selected", :id="group.key") - label.custom-control-label(v-once, :for="group.key") {{ group.label }} + input.custom-control-input(type="checkbox", v-model="viewOptions[group.key].selected", :id="groupBy + group.key") + label.custom-control-label(v-once, :for="groupBy + group.key") {{ group.label }} .standard-page .clearfix From 5265e6d321293ae3d677b1ce2d468438a77553f9 Mon Sep 17 00:00:00 2001 From: Alys Date: Wed, 28 Feb 2018 19:45:07 +1000 Subject: [PATCH 14/15] add swear words - TRIGGER / CONTENT WARNING: assault, slurs, swearwords, etc --- website/server/libs/bannedWords.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/website/server/libs/bannedWords.js b/website/server/libs/bannedWords.js index 55aa88abd3..bdb78503b2 100644 --- a/website/server/libs/bannedWords.js +++ b/website/server/libs/bannedWords.js @@ -80,6 +80,10 @@ let bannedWords = [ 'oh, god', 'g\\*d', + 'bugger', + 'buggery', + 'buggering', + 'buggered', 'shit', 'shitty', 'shitting', From dee81ee6c935b7c7dd93575a4db8505819741594 Mon Sep 17 00:00:00 2001 From: Keith Holliday Date: Wed, 28 Feb 2018 08:17:54 -0700 Subject: [PATCH 15/15] Reversed order to put newest on top (#10059) --- website/client/components/userMenu/inbox.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/client/components/userMenu/inbox.vue b/website/client/components/userMenu/inbox.vue index c7372402e2..58428090c7 100644 --- a/website/client/components/userMenu/inbox.vue +++ b/website/client/components/userMenu/inbox.vue @@ -267,7 +267,7 @@ export default { return moment(o.date).toDate(); }]); - return conversations; + return conversations.reverse(); }, filtersConversations () { if (!this.search) return this.conversations;