diff --git a/migrations/20180125_clean_new_notifications.js b/migrations/20180125_clean_new_notifications.js new file mode 100644 index 0000000000..4850c0c726 --- /dev/null +++ b/migrations/20180125_clean_new_notifications.js @@ -0,0 +1,93 @@ +const UserNotification = require('../website/server/models/userNotification').model; +const content = require('../website/common/script/content/index'); + +const migrationName = '20180125_clean_new_migrations'; +const authorName = 'paglias'; // in case script author needs to know when their ... +const authorUuid = 'ed4c688c-6652-4a92-9d03-a5a79844174a'; // ... own data is done + +/* + * Clean new migration types for processed users + */ + +const monk = require('monk'); +const connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE +const dbUsers = monk(connectionString).get('users', { castIds: false }); + +const progressCount = 1000; +let count = 0; + +function updateUser (user) { + count++; + + const types = ['NEW_MYSTERY_ITEMS', 'CARD_RECEIVED', 'NEW_CHAT_MESSAGE']; + + dbUsers.update({_id: user._id}, { + $pull: {notifications: { type: {$in: types } } }, + $set: {migration: migrationName}, + }); + + if (count % progressCount === 0) console.warn(`${count } ${ user._id}`); + if (user._id === authorUuid) console.warn(`${authorName } processed`); +} + +function exiting (code, msg) { + code = code || 0; // 0 = success + if (code && !msg) { + msg = 'ERROR!'; + } + if (msg) { + if (code) { + console.error(msg); + } else { + console.log(msg); + } + } + process.exit(code); +} + +function displayData () { + console.warn(`\n${ count } users processed\n`); + return exiting(0); +} + +function updateUsers (users) { + if (!users || users.length === 0) { + console.warn('All appropriate users found and modified.'); + displayData(); + return; + } + + const userPromises = users.map(updateUser); + const lastUser = users[users.length - 1]; + + return Promise.all(userPromises) + .then(() => { + processUsers(lastUser._id); + }); +} + +function processUsers (lastId) { + // specify a query to limit the affected users (empty for all users): + const query = { + migration: {$ne: migrationName}, + 'auth.timestamps.loggedin': {$gt: new Date('2010-01-24')}, + }; + + if (lastId) { + query._id = { + $gt: lastId, + }; + } + + dbUsers.find(query, { + sort: {_id: 1}, + limit: 250, + }) + .then(updateUsers) + .catch((err) => { + console.log(err); + return exiting(1, `ERROR! ${ err}`); + }); +} + +module.exports = processUsers; diff --git a/migrations/20180125_notifications.js b/migrations/20180125_notifications.js new file mode 100644 index 0000000000..7e7f697dab --- /dev/null +++ b/migrations/20180125_notifications.js @@ -0,0 +1,149 @@ +const UserNotification = require('../website/server/models/userNotification').model; +const content = require('../website/common/script/content/index'); + +const migrationName = '20180125_migrations-v2'; +const authorName = 'paglias'; // in case script author needs to know when their ... +const authorUuid = 'ed4c688c-6652-4a92-9d03-a5a79844174a'; // ... own data is done + +/* + * Migrate to new notifications system + */ + +const monk = require('monk'); +const connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE +const dbUsers = monk(connectionString).get('users', { castIds: false }); + +const progressCount = 1000; +let count = 0; + +function updateUser (user) { + count++; + + const notifications = []; + + // UNALLOCATED_STATS_POINTS skipped because added on each save + // NEW_STUFF skipped because it's a new type + // GROUP_TASK_NEEDS_WORK because it's a new type + // NEW_INBOX_MESSAGE not implemented yet + + + // NEW_MYSTERY_ITEMS + const mysteryItems = user.purchased && user.purchased.plan && user.purchased.plan.mysteryItems; + if (Array.isArray(mysteryItems) && mysteryItems.length > 0) { + const newMysteryNotif = new UserNotification({ + type: 'NEW_MYSTERY_ITEMS', + data: { + items: mysteryItems, + }, + }).toJSON(); + notifications.push(newMysteryNotif); + } + + // CARD_RECEIVED + Object.keys(content.cardTypes).forEach(cardType => { + const existingCards = user.items.special[`${cardType}Received`] || []; + existingCards.forEach(sender => { + const newNotif = new UserNotification({ + type: 'CARD_RECEIVED', + data: { + card: cardType, + from: { + // id is missing in old notifications + name: sender, + }, + }, + }).toJSON(); + + notifications.push(newNotif); + }); + }); + + // NEW_CHAT_MESSAGE + Object.keys(user.newMessages).forEach(groupId => { + const existingNotif = user.newMessages[groupId]; + + if (existingNotif) { + const newNotif = new UserNotification({ + type: 'NEW_CHAT_MESSAGE', + data: { + group: { + id: groupId, + name: existingNotif.name, + }, + }, + }).toJSON(); + + notifications.push(newNotif); + } + }); + + dbUsers.update({_id: user._id}, { + $push: {notifications: { $each: notifications } }, + $set: {migration: migrationName}, + }); + + if (count % progressCount === 0) console.warn(`${count } ${ user._id}`); + if (user._id === authorUuid) console.warn(`${authorName } processed`); +} + +function exiting (code, msg) { + code = code || 0; // 0 = success + if (code && !msg) { + msg = 'ERROR!'; + } + if (msg) { + if (code) { + console.error(msg); + } else { + console.log(msg); + } + } + process.exit(code); +} + +function displayData () { + console.warn(`\n${ count } users processed\n`); + return exiting(0); +} + +function updateUsers (users) { + if (!users || users.length === 0) { + console.warn('All appropriate users found and modified.'); + displayData(); + return; + } + + const userPromises = users.map(updateUser); + const lastUser = users[users.length - 1]; + + return Promise.all(userPromises) + .then(() => { + processUsers(lastUser._id); + }); +} + +function processUsers (lastId) { + // specify a query to limit the affected users (empty for all users): + const query = { + migration: {$ne: migrationName}, + 'auth.timestamps.loggedin': {$gt: new Date('2010-01-24')}, + }; + + if (lastId) { + query._id = { + $gt: lastId, + }; + } + + dbUsers.find(query, { + sort: {_id: 1}, + limit: 250, + }) + .then(updateUsers) + .catch((err) => { + console.log(err); + return exiting(1, `ERROR! ${ err}`); + }); +} + +module.exports = processUsers; diff --git a/migrations/migration-runner.js b/migrations/migration-runner.js index b594e403d2..2b030b88e6 100644 --- a/migrations/migration-runner.js +++ b/migrations/migration-runner.js @@ -17,5 +17,5 @@ function setUpServer () { setUpServer(); // Replace this with your migration -const processUsers = require('./tasks/tasks-set-everyX'); +const processUsers = require('./20180125_clean_new_notifications.js'); processUsers(); diff --git a/migrations/mystery_items.js b/migrations/mystery_items.js index df34e173ef..28da1c7ed6 100644 --- a/migrations/mystery_items.js +++ b/migrations/mystery_items.js @@ -1,10 +1,23 @@ +var UserNotification = require('../website/server/models/userNotification').model + var _id = ''; + +var items = ['back_mystery_201801','headAccessory_mystery_201801'] + var update = { $addToSet: { 'purchased.plan.mysteryItems':{ - $each:['back_mystery_201801','headAccessory_mystery_201801'] + $each: items, } - } + }, + $push: { + notifications: (new UserNotification({ + type: 'NEW_MYSTERY_ITEMS', + data: { + items: items, + }, + })).toJSON(), + }, }; /*var update = { diff --git a/package-lock.json b/package-lock.json index 0cea35de7d..3dce45a455 100644 --- a/package-lock.json +++ b/package-lock.json @@ -408,6 +408,14 @@ } } }, + "ansi-colors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.0.1.tgz", + "integrity": "sha512-yopkAU0ZD/WQ56Tms3xLn6jRuX3SyUMAVi0FdmDIbmmnHW3jHiI1sQFdUl3gfVddjnrsP3Y6ywFKvCRopvoVIA==", + "requires": { + "ansi-wrap": "0.1.0" + } + }, "ansi-escapes": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz", @@ -739,7 +747,7 @@ "integrity": "sha1-Hb0cg1ZY41zj+ZhAmdsAWFx4IBQ=", "requires": { "browserslist": "1.7.7", - "caniuse-db": "1.0.30000793", + "caniuse-db": "1.0.30000799", "normalize-range": "0.1.2", "num2fraction": "1.2.2", "postcss": "5.2.18", @@ -747,9 +755,9 @@ } }, "aws-sdk": { - "version": "2.185.0", - "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.185.0.tgz", - "integrity": "sha1-pXC4yxoIPYjtkPX2KRRLHc9uFDQ=", + "version": "2.188.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.188.0.tgz", + "integrity": "sha1-kGKrx9umOTRZ+i80I89dKU8ARhE=", "requires": { "buffer": "4.9.1", "events": "1.1.1", @@ -806,7 +814,7 @@ "resolved": "https://registry.npmjs.org/axios/-/axios-0.16.2.tgz", "integrity": "sha1-uk+S8XFn37q0CYN4VFS5rBScPG0=", "requires": { - "follow-redirects": "1.3.0", + "follow-redirects": "1.4.1", "is-buffer": "1.1.6" } }, @@ -1663,9 +1671,9 @@ "integrity": "sha512-gulJE5dGFo6Q61V/whS6VM4WIyrlydXfCgkE+Gxe5hjrJ8rXLLZlALq7zq2RPhOc45PSwQpJkrTnc2KgD6cvmA==" }, "bootstrap-vue": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bootstrap-vue/-/bootstrap-vue-1.5.0.tgz", - "integrity": "sha512-tQ22yKZbvKd2HWbFey1WVXbjJY/3zvEep+ODt/EPC4BFSCcPzq7s6Em/boQ6tIE4xdckySDSgGAtxPcwo4Sz9g==", + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/bootstrap-vue/-/bootstrap-vue-1.5.1.tgz", + "integrity": "sha512-bkob7vTHA5VZN6U0Wj34Yj+6jNtqAtc6MwcsLdBz78fcy8Ju5tlYUYMDUefQ0rQH7hhdtFDn9GuDiqhUPmE1sA==", "requires": { "lodash.startcase": "4.4.0", "opencollective": "1.0.3", @@ -1858,7 +1866,7 @@ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-1.7.7.tgz", "integrity": "sha1-C9dnBCWL6CmyOYu1Dkti0aFmsLk=", "requires": { - "caniuse-db": "1.0.30000793", + "caniuse-db": "1.0.30000799", "electron-to-chromium": "1.3.31" } }, @@ -2020,15 +2028,15 @@ "integrity": "sha1-tTTnxzTE+B7F++isoq0kNUuWLGw=", "requires": { "browserslist": "1.7.7", - "caniuse-db": "1.0.30000793", + "caniuse-db": "1.0.30000799", "lodash.memoize": "4.1.2", "lodash.uniq": "4.5.0" } }, "caniuse-db": { - "version": "1.0.30000793", - "resolved": "https://registry.npmjs.org/caniuse-db/-/caniuse-db-1.0.30000793.tgz", - "integrity": "sha1-PADGbkI6ehkHx92Wdpp4sq+opy4=" + "version": "1.0.30000799", + "resolved": "https://registry.npmjs.org/caniuse-db/-/caniuse-db-1.0.30000799.tgz", + "integrity": "sha1-CQSVPek/P0kmR+WMGhvaenOgyws=" }, "capture-stack-trace": { "version": "1.0.0", @@ -2273,9 +2281,9 @@ } }, "chromedriver": { - "version": "2.34.1", - "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-2.34.1.tgz", - "integrity": "sha512-ivXrPKKtnX442J8Lkbhb8hJ5+lelzAqrAI9VjVs3/iujm396JnJYXGGGjniPXvQeLVE3HDIWwsHu8goIUq3rMQ==", + "version": "2.35.0", + "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-2.35.0.tgz", + "integrity": "sha512-zqvC/HKybRxiM68GzByvUaXxTmNCmpETvLQIM92IEdrQxPnONKt3ZdTsiwxmGrL2ZIDbr9OEHJljmhZZMEsFPw==", "dev": true, "requires": { "del": "3.0.0", @@ -2820,7 +2828,7 @@ "dev": true, "requires": { "debug": "2.6.9", - "encodeurl": "1.0.1", + "encodeurl": "1.0.2", "escape-html": "1.0.3", "on-finished": "2.3.0", "parseurl": "1.3.2", @@ -3115,7 +3123,7 @@ "cipher-base": "1.0.4", "inherits": "2.0.3", "ripemd160": "2.0.1", - "sha.js": "2.4.9" + "sha.js": "2.4.10" } }, "create-hmac": { @@ -3128,7 +3136,7 @@ "inherits": "2.0.3", "ripemd160": "2.0.1", "safe-buffer": "5.1.1", - "sha.js": "2.4.9" + "sha.js": "2.4.10" } }, "cross-env": { @@ -4192,9 +4200,9 @@ } }, "domain-browser": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.1.7.tgz", - "integrity": "sha1-hnqksJP6oF8d4IwG9NeyH9+GmLw=" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", + "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==" }, "domain-middleware": { "version": "0.1.0", @@ -4627,9 +4635,9 @@ "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=" }, "encodeurl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.1.tgz", - "integrity": "sha1-eePVhlU0aQn+bw9Fpd5oEDspTSA=" + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" }, "encoding": { "version": "0.1.12", @@ -4967,7 +4975,7 @@ "imurmurhash": "0.1.4", "inquirer": "0.12.0", "is-my-json-valid": "2.17.1", - "is-resolvable": "1.0.1", + "is-resolvable": "1.1.0", "js-yaml": "3.7.0", "json-stable-stringify": "1.0.1", "levn": "0.3.0", @@ -5095,7 +5103,7 @@ "integrity": "sha1-fPq7yR9b084PAMCpo1Ccorl3O+0=", "dev": true, "requires": { - "eslint-plugin-lodash": "2.5.0", + "eslint-plugin-lodash": "2.5.1", "eslint-plugin-mocha": "4.11.0" } }, @@ -5180,9 +5188,9 @@ } }, "eslint-plugin-lodash": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-lodash/-/eslint-plugin-lodash-2.5.0.tgz", - "integrity": "sha512-CmNYc6sriYcPwwyv+wUtj6KowIhg9HygMi8ow1Q8qfDM1Y7WaHgZj/kPpT9tpjTJkTO2+goqXXzJRj43m5Eang==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-lodash/-/eslint-plugin-lodash-2.5.1.tgz", + "integrity": "sha512-Z1L5ZNE49+2FaEMCoAzik7RQGfuoNvMt/4JdU2XZWyz7juW1qCbxANwfBlfZPe2p1X36JKZL6enJzXZGCBADeQ==", "dev": true, "optional": true, "requires": { @@ -5573,7 +5581,7 @@ "cookie-signature": "1.0.6", "debug": "2.2.0", "depd": "1.1.2", - "encodeurl": "1.0.1", + "encodeurl": "1.0.2", "escape-html": "1.0.3", "etag": "1.7.0", "finalhandler": "0.5.1", @@ -6109,9 +6117,9 @@ "integrity": "sha1-2uRqnXj74lKSJYzB54CkHZXAN4I=" }, "follow-redirects": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.3.0.tgz", - "integrity": "sha1-9oSHH8EW0uMp/aVe9naH9Pq8kFw=", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.4.1.tgz", + "integrity": "sha512-uxYePVPogtya1ktGnAAXOacnbIuRMB4dkvqeNz2qTtTQsuzSfbDolV+wMMKxAmCx0bLgAKLbBOkjItMbbkR1vg==", "requires": { "debug": "3.1.0" }, @@ -6244,14 +6252,12 @@ "dependencies": { "abbrev": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.0.tgz", - "integrity": "sha1-0FVMIlZjbi9W58LlrRg/hZQo2B8=", + "bundled": true, "optional": true }, "ajv": { "version": "4.11.8", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", - "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", + "bundled": true, "optional": true, "requires": { "co": "4.6.0", @@ -6260,19 +6266,16 @@ }, "ansi-regex": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + "bundled": true }, "aproba": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.1.1.tgz", - "integrity": "sha1-ldNgDwdxCqDpKYxyatXs8urLq6s=", + "bundled": true, "optional": true }, "are-we-there-yet": { "version": "1.1.4", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz", - "integrity": "sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0=", + "bundled": true, "optional": true, "requires": { "delegates": "1.0.0", @@ -6281,43 +6284,36 @@ }, "asn1": { "version": "0.2.3", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", - "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=", + "bundled": true, "optional": true }, "assert-plus": { "version": "0.2.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz", - "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=", + "bundled": true, "optional": true }, "asynckit": { "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "bundled": true, "optional": true }, "aws-sign2": { "version": "0.6.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz", - "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=", + "bundled": true, "optional": true }, "aws4": { "version": "1.6.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", - "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=", + "bundled": true, "optional": true }, "balanced-match": { "version": "0.4.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz", - "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg=" + "bundled": true }, "bcrypt-pbkdf": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", - "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", + "bundled": true, "optional": true, "requires": { "tweetnacl": "0.14.5" @@ -6325,24 +6321,21 @@ }, "block-stream": { "version": "0.0.9", - "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", - "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=", + "bundled": true, "requires": { "inherits": "2.0.3" } }, "boom": { "version": "2.10.1", - "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", - "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=", + "bundled": true, "requires": { "hoek": "2.16.3" } }, "brace-expansion": { "version": "1.1.7", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.7.tgz", - "integrity": "sha1-Pv/DxQ4ABTH7cg6v+A8K6O8jz1k=", + "bundled": true, "requires": { "balanced-match": "0.4.2", "concat-map": "0.0.1" @@ -6350,61 +6343,51 @@ }, "buffer-shims": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-shims/-/buffer-shims-1.0.0.tgz", - "integrity": "sha1-mXjOMXOIxkmth5MCjDR37wRKi1E=" + "bundled": true }, "caseless": { "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "bundled": true, "optional": true }, "co": { "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "bundled": true, "optional": true }, "code-point-at": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" + "bundled": true }, "combined-stream": { "version": "1.0.5", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", - "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=", + "bundled": true, "requires": { "delayed-stream": "1.0.0" } }, "concat-map": { "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + "bundled": true }, "console-control-strings": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" + "bundled": true }, "core-util-is": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + "bundled": true }, "cryptiles": { "version": "2.0.5", - "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", - "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=", + "bundled": true, "requires": { "boom": "2.10.1" } }, "dashdash": { "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "bundled": true, "optional": true, "requires": { "assert-plus": "1.0.0" @@ -6412,16 +6395,14 @@ "dependencies": { "assert-plus": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "bundled": true, "optional": true } } }, "debug": { "version": "2.6.8", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", - "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=", + "bundled": true, "optional": true, "requires": { "ms": "2.0.0" @@ -6429,31 +6410,26 @@ }, "deep-extend": { "version": "0.4.2", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.2.tgz", - "integrity": "sha1-SLaZwn4zS/ifEIkr5DL25MfTSn8=", + "bundled": true, "optional": true }, "delayed-stream": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + "bundled": true }, "delegates": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", + "bundled": true, "optional": true }, "detect-libc": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.2.tgz", - "integrity": "sha1-ca1dIEvxempsqPRQxhRUBm70YeE=", + "bundled": true, "optional": true }, "ecc-jsbn": { "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", - "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", + "bundled": true, "optional": true, "requires": { "jsbn": "0.1.1" @@ -6461,25 +6437,21 @@ }, "extend": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", - "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=", + "bundled": true, "optional": true }, "extsprintf": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.0.2.tgz", - "integrity": "sha1-4QgOBljjALBilJkMxw4VAiNf1VA=" + "bundled": true }, "forever-agent": { "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "bundled": true, "optional": true }, "form-data": { "version": "2.1.4", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz", - "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=", + "bundled": true, "optional": true, "requires": { "asynckit": "0.4.0", @@ -6489,13 +6461,11 @@ }, "fs.realpath": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + "bundled": true }, "fstream": { "version": "1.0.11", - "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz", - "integrity": "sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=", + "bundled": true, "requires": { "graceful-fs": "4.1.11", "inherits": "2.0.3", @@ -6505,8 +6475,7 @@ }, "fstream-ignore": { "version": "1.0.5", - "resolved": "https://registry.npmjs.org/fstream-ignore/-/fstream-ignore-1.0.5.tgz", - "integrity": "sha1-nDHa40dnAY/h0kmyTa2mfQktoQU=", + "bundled": true, "optional": true, "requires": { "fstream": "1.0.11", @@ -6516,8 +6485,7 @@ }, "gauge": { "version": "2.7.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", - "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "bundled": true, "optional": true, "requires": { "aproba": "1.1.1", @@ -6532,8 +6500,7 @@ }, "getpass": { "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "bundled": true, "optional": true, "requires": { "assert-plus": "1.0.0" @@ -6541,16 +6508,14 @@ "dependencies": { "assert-plus": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "bundled": true, "optional": true } } }, "glob": { "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "bundled": true, "requires": { "fs.realpath": "1.0.0", "inflight": "1.0.6", @@ -6562,19 +6527,16 @@ }, "graceful-fs": { "version": "4.1.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", - "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" + "bundled": true }, "har-schema": { "version": "1.0.5", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-1.0.5.tgz", - "integrity": "sha1-0mMTX0MwfALGAq/I/pWXDAFRNp4=", + "bundled": true, "optional": true }, "har-validator": { "version": "4.2.1", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-4.2.1.tgz", - "integrity": "sha1-M0gdDxu/9gDdID11gSpqX7oALio=", + "bundled": true, "optional": true, "requires": { "ajv": "4.11.8", @@ -6583,14 +6545,12 @@ }, "has-unicode": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", + "bundled": true, "optional": true }, "hawk": { "version": "3.1.3", - "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", - "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=", + "bundled": true, "requires": { "boom": "2.10.1", "cryptiles": "2.0.5", @@ -6600,13 +6560,11 @@ }, "hoek": { "version": "2.16.3", - "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", - "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=" + "bundled": true }, "http-signature": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", - "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=", + "bundled": true, "optional": true, "requires": { "assert-plus": "0.2.0", @@ -6616,8 +6574,7 @@ }, "inflight": { "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "bundled": true, "requires": { "once": "1.4.0", "wrappy": "1.0.2" @@ -6625,44 +6582,37 @@ }, "inherits": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + "bundled": true }, "ini": { "version": "1.3.4", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.4.tgz", - "integrity": "sha1-BTfLedr1m1mhpRff9wbIbsA5Fi4=", + "bundled": true, "optional": true }, "is-fullwidth-code-point": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "bundled": true, "requires": { "number-is-nan": "1.0.1" } }, "is-typedarray": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "bundled": true, "optional": true }, "isarray": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + "bundled": true }, "isstream": { "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "bundled": true, "optional": true }, "jodid25519": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/jodid25519/-/jodid25519-1.0.2.tgz", - "integrity": "sha1-BtSRIlUJNBlHfUJWM2BuDpB4KWc=", + "bundled": true, "optional": true, "requires": { "jsbn": "0.1.1" @@ -6670,20 +6620,17 @@ }, "jsbn": { "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "bundled": true, "optional": true }, "json-schema": { "version": "0.2.3", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", + "bundled": true, "optional": true }, "json-stable-stringify": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", - "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", + "bundled": true, "optional": true, "requires": { "jsonify": "0.0.0" @@ -6691,20 +6638,17 @@ }, "json-stringify-safe": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "bundled": true, "optional": true }, "jsonify": { "version": "0.0.0", - "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", - "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", + "bundled": true, "optional": true }, "jsprim": { "version": "1.4.0", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.0.tgz", - "integrity": "sha1-o7h+QCmNjDgFUtjMdiigu5WiKRg=", + "bundled": true, "optional": true, "requires": { "assert-plus": "1.0.0", @@ -6715,56 +6659,48 @@ "dependencies": { "assert-plus": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "bundled": true, "optional": true } } }, "mime-db": { "version": "1.27.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.27.0.tgz", - "integrity": "sha1-gg9XIpa70g7CXtVeW13oaeVDbrE=" + "bundled": true }, "mime-types": { "version": "2.1.15", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.15.tgz", - "integrity": "sha1-pOv1BkCUVpI3uM9wBGd20J/JKu0=", + "bundled": true, "requires": { "mime-db": "1.27.0" } }, "minimatch": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "bundled": true, "requires": { "brace-expansion": "1.1.7" } }, "minimist": { "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + "bundled": true }, "mkdirp": { "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "bundled": true, "requires": { "minimist": "0.0.8" } }, "ms": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "bundled": true, "optional": true }, "node-pre-gyp": { "version": "0.6.39", - "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.6.39.tgz", - "integrity": "sha512-OsJV74qxnvz/AMGgcfZoDaeDXKD3oY3QVIbBmwszTFkRisTSXbMQyn4UWzUMOtA5SVhrBZOTp0wcoSBgfMfMmQ==", + "bundled": true, "optional": true, "requires": { "detect-libc": "1.0.2", @@ -6782,8 +6718,7 @@ }, "nopt": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", - "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", + "bundled": true, "optional": true, "requires": { "abbrev": "1.1.0", @@ -6792,8 +6727,7 @@ }, "npmlog": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.0.tgz", - "integrity": "sha512-ocolIkZYZt8UveuiDS0yAkkIjid1o7lPG8cYm05yNYzBn8ykQtaiPMEGp8fY9tKdDgm8okpdKzkvu1y9hUYugA==", + "bundled": true, "optional": true, "requires": { "are-we-there-yet": "1.1.4", @@ -6804,45 +6738,38 @@ }, "number-is-nan": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" + "bundled": true }, "oauth-sign": { "version": "0.8.2", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", - "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=", + "bundled": true, "optional": true }, "object-assign": { "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "bundled": true, "optional": true }, "once": { "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "bundled": true, "requires": { "wrappy": "1.0.2" } }, "os-homedir": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "bundled": true, "optional": true }, "os-tmpdir": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "bundled": true, "optional": true }, "osenv": { "version": "0.1.4", - "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.4.tgz", - "integrity": "sha1-Qv5tWVPfBsgGS+bxdsPQWqqjRkQ=", + "bundled": true, "optional": true, "requires": { "os-homedir": "1.0.2", @@ -6851,36 +6778,30 @@ }, "path-is-absolute": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + "bundled": true }, "performance-now": { "version": "0.2.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz", - "integrity": "sha1-M+8wxcd9TqIcWlOGnZG1bY8lVeU=", + "bundled": true, "optional": true }, "process-nextick-args": { "version": "1.0.7", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", - "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=" + "bundled": true }, "punycode": { "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "bundled": true, "optional": true }, "qs": { "version": "6.4.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz", - "integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM=", + "bundled": true, "optional": true }, "rc": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.1.tgz", - "integrity": "sha1-LgPo5C7kULjLPc5lvhv4l04d/ZU=", + "bundled": true, "optional": true, "requires": { "deep-extend": "0.4.2", @@ -6891,16 +6812,14 @@ "dependencies": { "minimist": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "bundled": true, "optional": true } } }, "readable-stream": { "version": "2.2.9", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.9.tgz", - "integrity": "sha1-z3jsb0ptHrQ9JkiMrJfwQudLf8g=", + "bundled": true, "requires": { "buffer-shims": "1.0.0", "core-util-is": "1.0.2", @@ -6913,8 +6832,7 @@ }, "request": { "version": "2.81.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.81.0.tgz", - "integrity": "sha1-xpKJRqDgbF+Nb4qTM0af/aRimKA=", + "bundled": true, "optional": true, "requires": { "aws-sign2": "0.6.0", @@ -6943,47 +6861,40 @@ }, "rimraf": { "version": "2.6.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.1.tgz", - "integrity": "sha1-wjOOxkPfeht/5cVPqG9XQopV8z0=", + "bundled": true, "requires": { "glob": "7.1.2" } }, "safe-buffer": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.0.1.tgz", - "integrity": "sha1-0mPKVGls2KMGtcplUekt5XkY++c=" + "bundled": true }, "semver": { "version": "5.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", - "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=", + "bundled": true, "optional": true }, "set-blocking": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "bundled": true, "optional": true }, "signal-exit": { "version": "3.0.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "bundled": true, "optional": true }, "sntp": { "version": "1.0.9", - "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", - "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=", + "bundled": true, "requires": { "hoek": "2.16.3" } }, "sshpk": { "version": "1.13.0", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.0.tgz", - "integrity": "sha1-/yo+T9BEl1Vf7Zezmg/YL6+zozw=", + "bundled": true, "optional": true, "requires": { "asn1": "0.2.3", @@ -6999,16 +6910,14 @@ "dependencies": { "assert-plus": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "bundled": true, "optional": true } } }, "string-width": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "bundled": true, "requires": { "code-point-at": "1.1.0", "is-fullwidth-code-point": "1.0.0", @@ -7017,36 +6926,31 @@ }, "string_decoder": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.1.tgz", - "integrity": "sha1-YuIA8DmVWmgQ2N8KM//A8BNmLZg=", + "bundled": true, "requires": { "safe-buffer": "5.0.1" } }, "stringstream": { "version": "0.0.5", - "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", - "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=", + "bundled": true, "optional": true }, "strip-ansi": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "bundled": true, "requires": { "ansi-regex": "2.1.1" } }, "strip-json-comments": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "bundled": true, "optional": true }, "tar": { "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz", - "integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=", + "bundled": true, "requires": { "block-stream": "0.0.9", "fstream": "1.0.11", @@ -7055,8 +6959,7 @@ }, "tar-pack": { "version": "3.4.0", - "resolved": "https://registry.npmjs.org/tar-pack/-/tar-pack-3.4.0.tgz", - "integrity": "sha1-I74tf2cagzk3bL2wuP4/3r8xeYQ=", + "bundled": true, "optional": true, "requires": { "debug": "2.6.8", @@ -7071,8 +6974,7 @@ }, "tough-cookie": { "version": "2.3.2", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.2.tgz", - "integrity": "sha1-8IH3bkyFcg5sN6X6ztc3FQ2EByo=", + "bundled": true, "optional": true, "requires": { "punycode": "1.4.1" @@ -7080,8 +6982,7 @@ }, "tunnel-agent": { "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "bundled": true, "optional": true, "requires": { "safe-buffer": "5.0.1" @@ -7089,31 +6990,26 @@ }, "tweetnacl": { "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "bundled": true, "optional": true }, "uid-number": { "version": "0.0.6", - "resolved": "https://registry.npmjs.org/uid-number/-/uid-number-0.0.6.tgz", - "integrity": "sha1-DqEOgDXo61uOREnwbaHHMGY7qoE=", + "bundled": true, "optional": true }, "util-deprecate": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + "bundled": true }, "uuid": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.0.1.tgz", - "integrity": "sha1-ZUS7ot/ajBzxfmKaOjBeK7H+5sE=", + "bundled": true, "optional": true }, "verror": { "version": "1.3.6", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.3.6.tgz", - "integrity": "sha1-z/XfEpRtKX0rqu+qJoniW+AcAFw=", + "bundled": true, "optional": true, "requires": { "extsprintf": "1.0.2" @@ -7121,8 +7017,7 @@ }, "wide-align": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.2.tgz", - "integrity": "sha512-ijDLlyQ7s6x1JgCLur53osjm/UXUYD9+0PbYKrBsYisYXzCxN+HC3mYDNy/dWdmf3AwqwU3CXwDCvsNgGK1S0w==", + "bundled": true, "optional": true, "requires": { "string-width": "1.0.2" @@ -7130,8 +7025,7 @@ }, "wrappy": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + "bundled": true } } }, @@ -7602,9 +7496,9 @@ } }, "glogg": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/glogg/-/glogg-1.0.0.tgz", - "integrity": "sha1-f+DxmfV6yQbPUS/urY+Q7kooT8U=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/glogg/-/glogg-1.0.1.tgz", + "integrity": "sha512-ynYqXLoluBKf9XGR1gA59yEJisIL7YHEH4xr3ZziHB5/yl4qWfaK8Js9jGe6gBGCSCKVqiyO30WnRZADvemUNw==", "requires": { "sparkles": "1.0.0" } @@ -7675,13 +7569,13 @@ } }, "gulp-babel": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/gulp-babel/-/gulp-babel-6.1.2.tgz", - "integrity": "sha1-fAF25Lo/JExgWIoMSzIKRdGt784=", + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/gulp-babel/-/gulp-babel-6.1.3.tgz", + "integrity": "sha512-tm15R3rt4gO59WXCuqrwf4QXJM9VIJC+0J2NPYSC6xZn+cZRD5y5RPGAiHaDxCJq7Rz5BDljlrk3cEjWADF+wQ==", "requires": { "babel-core": "6.26.0", - "gulp-util": "3.0.8", "object-assign": "4.1.1", + "plugin-error": "1.0.1", "replace-ext": "0.0.1", "through2": "2.0.3", "vinyl-sourcemaps-apply": "0.2.1" @@ -7729,9 +7623,9 @@ "integrity": "sha1-OtRCh2PwXidk3sHGfYaNsnVoeBc=" }, "gulp-sourcemaps": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/gulp-sourcemaps/-/gulp-sourcemaps-1.12.0.tgz", - "integrity": "sha1-eG+XyUoPloSSRl1wVY4EJCxnlZg=", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/gulp-sourcemaps/-/gulp-sourcemaps-1.12.1.tgz", + "integrity": "sha1-tDfR89mAzyboEYSCNxjOFa5ll7Y=", "requires": { "@gulp-sourcemaps/map-sources": "1.0.0", "acorn": "4.0.13", @@ -7740,12 +7634,17 @@ "debug-fabulous": "0.0.4", "detect-newline": "2.1.0", "graceful-fs": "4.1.11", - "source-map": "0.5.7", + "source-map": "0.6.1", "strip-bom": "2.0.0", "through2": "2.0.3", "vinyl": "1.2.0" }, "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, "strip-bom": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", @@ -8039,7 +7938,7 @@ "resolved": "https://registry.npmjs.org/gulplog/-/gulplog-1.0.0.tgz", "integrity": "sha1-4oxNRdBey77YGDY86PnFkmIp/+U=", "requires": { - "glogg": "1.0.0" + "glogg": "1.0.1" } }, "gzip-size": { @@ -8071,7 +7970,7 @@ "entities": "1.1.1", "linkify-it": "2.0.3", "mdurl": "1.0.1", - "uc.micro": "1.0.3" + "uc.micro": "1.0.5" } } } @@ -8329,9 +8228,9 @@ } }, "hooks-fixed": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/hooks-fixed/-/hooks-fixed-2.0.0.tgz", - "integrity": "sha1-oB2JTVKsf2WZu7H2PfycQR33DLo=" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/hooks-fixed/-/hooks-fixed-1.2.0.tgz", + "integrity": "sha1-DSdy1NfWhf+SRHJKnwtbJVmqyWs=" }, "hosted-git-info": { "version": "2.5.0", @@ -8361,7 +8260,7 @@ "ncname": "1.0.0", "param-case": "2.1.1", "relateurl": "0.2.7", - "uglify-js": "3.3.8" + "uglify-js": "3.3.9" }, "dependencies": { "commander": { @@ -8375,9 +8274,9 @@ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" }, "uglify-js": { - "version": "3.3.8", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.3.8.tgz", - "integrity": "sha512-X0jAGtpSZRtd4RhbVNuGHyjZNa/h2MrVkKrR3Ew5iL2MJw6d7FmBke+fhVCALWySv1ygHnjjROG1KI1FAPvddw==", + "version": "3.3.9", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.3.9.tgz", + "integrity": "sha512-J2t8B5tj9JdPTW4+sNZXmiIWHzTvcoITkaqzTiilu/biZF/9crqf/Fi7k5hqbOmVRh9/hVNxAxBYIMF7N6SqMQ==", "requires": { "commander": "2.13.0", "source-map": "0.6.1" @@ -9139,9 +9038,9 @@ "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=" }, "in-app-purchase": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/in-app-purchase/-/in-app-purchase-1.8.5.tgz", - "integrity": "sha1-OuvbB+qKyv/t9omePCfjVdgeZyo=", + "version": "1.8.7", + "resolved": "https://registry.npmjs.org/in-app-purchase/-/in-app-purchase-1.8.7.tgz", + "integrity": "sha1-TckQ5TQ3/1noPRzPA9RbhWMK4Sw=", "requires": { "request": "2.83.0", "xml-crypto": "0.10.1", @@ -9720,9 +9619,9 @@ } }, "is-resolvable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.0.1.tgz", - "integrity": "sha512-y5CXYbzvB3jTnWAZH1Nl7ykUWb6T3BcTs56HUruwBf8MhF56n1HWqhDWnVFo8GHrUPDgvUUNVhrc2U8W7iqz5g==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", + "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", "dev": true }, "is-retry-allowed": { @@ -10076,9 +9975,9 @@ "integrity": "sha512-Ubldcmxp5np52/ENotGxlLe6aGMvmF4R8S6tZjsP6Knsaxd/xp3Zrh50cG93lR6nPXyUFwzN3ZSOQI0wRJNdGg==" }, "js-base64": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.4.1.tgz", - "integrity": "sha512-2h586r2I/CqU7z1aa1kBgWaVAXWAZK+zHnceGi/jFgn7+7VSluxYer/i3xOZVearCxxXvyDkLtTBo+OeJCA3kA==" + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.4.3.tgz", + "integrity": "sha512-H7ErYLM34CvDMto3GbD6xD0JLUGYXR3QTcH6B/tr4Hi/QpSThnCsIp+Sy5FRTw3B0d6py4HcNkW7nO/wdtGWEw==" }, "js-stringify": { "version": "1.0.2", @@ -10246,7 +10145,7 @@ "socket.io": "1.7.3", "source-map": "0.5.7", "tmp": "0.0.31", - "useragent": "2.2.1" + "useragent": "2.3.0" }, "dependencies": { "anymatch": { @@ -11128,7 +11027,7 @@ "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-2.0.3.tgz", "integrity": "sha1-2UpGSPmxwXnWT6lykSaL22zpQ08=", "requires": { - "uc.micro": "1.0.3" + "uc.micro": "1.0.5" } }, "load-json-file": { @@ -11789,7 +11688,7 @@ "entities": "1.1.1", "linkify-it": "2.0.3", "mdurl": "1.0.1", - "uc.micro": "1.0.3" + "uc.micro": "1.0.5" } }, "markdown-it-emoji": { @@ -12192,16 +12091,6 @@ "readable-stream": "2.2.7" }, "dependencies": { - "mongodb-core": { - "version": "2.1.18", - "resolved": "https://registry.npmjs.org/mongodb-core/-/mongodb-core-2.1.18.tgz", - "integrity": "sha1-TEYTm986HwMt7ZHbSfOO7AFlkFA=", - "dev": true, - "requires": { - "bson": "1.0.4", - "require_optional": "1.0.1" - } - }, "readable-stream": { "version": "2.2.7", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.7.tgz", @@ -12229,22 +12118,23 @@ } }, "mongodb-core": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/mongodb-core/-/mongodb-core-2.1.8.tgz", - "integrity": "sha1-sz4DcNClnZe2yx7GEFJ76elcosA=", + "version": "2.1.18", + "resolved": "https://registry.npmjs.org/mongodb-core/-/mongodb-core-2.1.18.tgz", + "integrity": "sha1-TEYTm986HwMt7ZHbSfOO7AFlkFA=", + "dev": true, "requires": { "bson": "1.0.4", "require_optional": "1.0.1" } }, "mongoose": { - "version": "4.8.7", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-4.8.7.tgz", - "integrity": "sha1-PGUOUcEj5TczuUeab8hXi+M+4G4=", + "version": "4.8.6", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-4.8.6.tgz", + "integrity": "sha1-8bN2skP+hvVNXVzdx7mQqt6Fjw0=", "requires": { "async": "2.1.4", "bson": "1.0.4", - "hooks-fixed": "2.0.0", + "hooks-fixed": "1.2.0", "kareem": "1.2.1", "mongodb": "2.2.24", "mpath": "0.2.1", @@ -12274,6 +12164,15 @@ "readable-stream": "2.1.5" } }, + "mongodb-core": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/mongodb-core/-/mongodb-core-2.1.8.tgz", + "integrity": "sha1-sz4DcNClnZe2yx7GEFJ76elcosA=", + "requires": { + "bson": "1.0.4", + "require_optional": "1.0.1" + } + }, "ms": { "version": "0.7.2", "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", @@ -12300,7 +12199,7 @@ "resolved": "https://registry.npmjs.org/mongoose-id-autoinc/-/mongoose-id-autoinc-2013.7.14-4.tgz", "integrity": "sha1-edd2NKYOl+gyHq88WQpKR/WgRl0=", "requires": { - "mongoose": "4.8.7" + "mongoose": "4.8.6" } }, "mongoskin": { @@ -12820,7 +12719,7 @@ "console-browserify": "1.1.0", "constants-browserify": "1.0.0", "crypto-browserify": "3.12.0", - "domain-browser": "1.1.7", + "domain-browser": "1.2.0", "events": "1.1.1", "https-browserify": "1.0.0", "os-browserify": "0.3.0", @@ -12832,7 +12731,7 @@ "stream-browserify": "2.0.1", "stream-http": "2.8.0", "string_decoder": "1.0.3", - "timers-browserify": "2.0.4", + "timers-browserify": "2.0.6", "tty-browserify": "0.0.0", "url": "0.11.0", "util": "0.10.3", @@ -13866,7 +13765,7 @@ "integrity": "sha1-iGmgQBJTZhxMTKPabCEh7VVfXu0=", "requires": { "got": "6.7.1", - "registry-auth-token": "3.3.1", + "registry-auth-token": "3.3.2", "registry-url": "3.1.0", "semver": "5.5.0" }, @@ -14207,7 +14106,7 @@ "create-hmac": "1.1.6", "ripemd160": "2.0.1", "safe-buffer": "5.1.1", - "sha.js": "2.4.9" + "sha.js": "2.4.10" } }, "pend": { @@ -14233,7 +14132,7 @@ "resolved": "https://registry.npmjs.org/phantomjs-prebuilt/-/phantomjs-prebuilt-2.1.16.tgz", "integrity": "sha1-79ISpKOWbTZHaE6ouniFSb4q7+8=", "requires": { - "es6-promise": "4.2.2", + "es6-promise": "4.2.4", "extract-zip": "1.6.6", "fs-extra": "1.0.0", "hasha": "2.2.0", @@ -14286,9 +14185,9 @@ } }, "es6-promise": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.2.tgz", - "integrity": "sha512-LSas5vsuA6Q4nEdf9wokY5/AJYXry98i0IzXsv49rYsgDGDNDPbqAYR1Pe23iFxygfbGZNR/5VrHXBCh2BhvUQ==" + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.4.tgz", + "integrity": "sha512-/NdNZVJg+uZgtm9eS3O6lrOLYmQag2DjdEXuPaHlZ6RuVqgqaVZfgYCepEIKsLqwdQArOPtC3XzRLqGGfT8KQQ==" }, "form-data": { "version": "2.3.1", @@ -14488,6 +14387,36 @@ "resolved": "https://registry.npmjs.org/pkginfo/-/pkginfo-0.4.1.tgz", "integrity": "sha1-tUGO8EOd5UJfxJlQQtztFPsqhP8=" }, + "plugin-error": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-1.0.1.tgz", + "integrity": "sha512-L1zP0dk7vGweZME2i+EeakvUNqSrdiI3F91TwEoYiGrAfUXmVv6fJIq4g82PAXxNsWOp0J7ZqQy/3Szz0ajTxA==", + "requires": { + "ansi-colors": "1.0.1", + "arr-diff": "4.0.0", + "arr-union": "3.1.0", + "extend-shallow": "3.0.2" + }, + "dependencies": { + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "requires": { + "assign-symbols": "1.0.0", + "is-extendable": "1.0.1" + } + }, + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "requires": { + "is-plain-object": "2.0.4" + } + } + } + }, "plur": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/plur/-/plur-2.1.2.tgz", @@ -14523,7 +14452,7 @@ "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", "requires": { "chalk": "1.1.3", - "js-base64": "2.4.1", + "js-base64": "2.4.3", "source-map": "0.5.7", "supports-color": "3.2.3" }, @@ -15849,9 +15778,9 @@ } }, "registry-auth-token": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.3.1.tgz", - "integrity": "sha1-+w0yie4Nmtosu1KvXf5mywcNMAY=", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.3.2.tgz", + "integrity": "sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ==", "requires": { "rc": "1.2.4", "safe-buffer": "5.1.1" @@ -16308,7 +16237,7 @@ "resolved": "https://registry.npmjs.org/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz", "integrity": "sha1-jrBtualyMzOCTT9VMGQRSYR85dE=", "requires": { - "js-base64": "2.4.1", + "js-base64": "2.4.3", "source-map": "0.4.4" }, "dependencies": { @@ -16395,7 +16324,7 @@ "debug": "2.2.0", "depd": "1.1.2", "destroy": "1.0.4", - "encodeurl": "1.0.1", + "encodeurl": "1.0.2", "escape-html": "1.0.3", "etag": "1.7.0", "fresh": "0.3.0", @@ -16464,7 +16393,7 @@ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.11.2.tgz", "integrity": "sha1-LPmIm9RDWjIMw2iVyapXvWYuasc=", "requires": { - "encodeurl": "1.0.1", + "encodeurl": "1.0.2", "escape-html": "1.0.3", "parseurl": "1.3.2", "send": "0.14.2" @@ -16510,9 +16439,9 @@ "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=" }, "sha.js": { - "version": "2.4.9", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.9.tgz", - "integrity": "sha512-G8zektVqbiPHrylgew9Zg1VRB1L/DtXNUVAM6q4QLy8NE3qtHlFXTf8VLL4k1Yl6c7NMjtZUTdXV+X44nFaT6A==", + "version": "2.4.10", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.10.tgz", + "integrity": "sha512-vnwmrFDlOExK4Nm16J2KMWHLrp14lBrjxMxBJpu++EnsuBmpiYaM/MEs46Vxxm/4FvdP5yTwuCTO9it5FSjrqA==", "requires": { "inherits": "2.0.3", "safe-buffer": "5.1.1" @@ -18057,9 +17986,9 @@ "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=" }, "timers-browserify": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.4.tgz", - "integrity": "sha512-uZYhyU3EX8O7HQP+J9fTVYwsq90Vr68xPEFo7yrVImIxYvHgukBEgOB/SgGoorWVTzGM/3Z+wUNnboA4M8jWrg==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.6.tgz", + "integrity": "sha512-HQ3nbYRAowdVd0ckGFvmJPPCOH/CHleFN/Y0YQCX1DVaB7t+KFvisuyN09fuP8Jtp1CpfSh8O8bMkHbdbPe6Pw==", "requires": { "setimmediate": "1.0.5" } @@ -18380,9 +18309,9 @@ "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" }, "uc.micro": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.3.tgz", - "integrity": "sha1-ftUNXg+an7ClczeSWfKndFjVAZI=" + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.5.tgz", + "integrity": "sha512-JoLI4g5zv5qNyT09f4YAvEZIIV1oOjqnewYg5D38dkQljIzpPT296dbIGvKro3digYI1bkb7W6EP1y4uDlmzLg==" }, "uglify-js": { "version": "2.6.4", @@ -18818,19 +18747,12 @@ "integrity": "sha1-K1viOjK2Onyd640PKNSFcko98ZA=" }, "useragent": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/useragent/-/useragent-2.2.1.tgz", - "integrity": "sha1-z1k+9PLRdYdei7ZY6pLhik/QbY4=", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/useragent/-/useragent-2.3.0.tgz", + "integrity": "sha512-4AoH4pxuSvHCjqLO04sU6U/uE65BYza8l/KKBS0b0hnUPWi+cQ2BpeTEwejCSx9SPV5/U03nniDTrWx5NrmKdw==", "requires": { - "lru-cache": "2.2.4", + "lru-cache": "4.1.1", "tmp": "0.0.33" - }, - "dependencies": { - "lru-cache": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.2.4.tgz", - "integrity": "sha1-bGWGGb7PFAMdDQtZSxYELOTcBj0=" - } } }, "util": { @@ -19072,7 +18994,7 @@ "resolve": "1.5.0", "source-map": "0.6.1", "vue-hot-reload-api": "2.2.4", - "vue-style-loader": "3.0.3", + "vue-style-loader": "3.1.1", "vue-template-es2015-compiler": "1.6.0" }, "dependencies": { @@ -19158,9 +19080,9 @@ "integrity": "sha512-vLLoY452L+JBpALMP5UHum9+7nzR9PeIBCghU9ZtJ1eWm6ieUI8Zb/DI3MYxH32bxkjzYV1LRjNv4qr8d+uX/w==" }, "vue-style-loader": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/vue-style-loader/-/vue-style-loader-3.0.3.tgz", - "integrity": "sha512-P/ihpaZKU23T1kq3E0y4c+F8sbm1HQO69EFYoLoGMSGVAHroHsGir/WQ9qUavP8dyFYHmXenzHaJ/nqd8vfaxw==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/vue-style-loader/-/vue-style-loader-3.1.1.tgz", + "integrity": "sha512-AX0tRYm5sQUn2G1gYwyFhreU5/2+oFEf0ddsiF1SyRv6bycY1ntqNu7r3CF92KxTXbmRzfm9bVz3Ys/xm+kQQg==", "requires": { "hash-sum": "1.0.2", "loader-utils": "1.1.0" @@ -19540,7 +19462,7 @@ "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "1.1.2", - "encodeurl": "1.0.1", + "encodeurl": "1.0.2", "escape-html": "1.0.3", "etag": "1.8.1", "finalhandler": "1.1.0", @@ -19570,7 +19492,7 @@ "dev": true, "requires": { "debug": "2.6.9", - "encodeurl": "1.0.1", + "encodeurl": "1.0.2", "escape-html": "1.0.3", "on-finished": "2.3.0", "parseurl": "1.3.2", @@ -19621,7 +19543,7 @@ "debug": "2.6.9", "depd": "1.1.2", "destroy": "1.0.4", - "encodeurl": "1.0.1", + "encodeurl": "1.0.2", "escape-html": "1.0.3", "etag": "1.8.1", "fresh": "0.5.2", @@ -19639,7 +19561,7 @@ "integrity": "sha512-hSMUZrsPa/I09VYFJwa627JJkNs0NrfL1Uzuup+GqHfToR2KcsXFymXSV90hoyw3M+msjFuQly+YzIH/q0MGlQ==", "dev": true, "requires": { - "encodeurl": "1.0.1", + "encodeurl": "1.0.2", "escape-html": "1.0.3", "parseurl": "1.3.2", "send": "0.16.1" diff --git a/package.json b/package.json index 17460cc059..d4409f4476 100644 --- a/package.json +++ b/package.json @@ -67,7 +67,7 @@ "method-override": "^2.3.5", "moment": "^2.13.0", "moment-recur": "git://github.com/habitrpg/moment-recur.git#f147ef27bbc26ca67638385f3db4a44084c76626", - "mongoose": "~4.8.6", + "mongoose": "^4.8.6", "mongoose-id-autoinc": "~2013.7.14-4", "morgan": "^1.7.0", "nconf": "~0.8.2", diff --git a/test/api/v3/integration/chat/POST-chat.test.js b/test/api/v3/integration/chat/POST-chat.test.js index 9255772ad2..fd9569bc8c 100644 --- a/test/api/v3/integration/chat/POST-chat.test.js +++ b/test/api/v3/integration/chat/POST-chat.test.js @@ -426,6 +426,9 @@ describe('POST /chat', () => { expect(message.message.id).to.exist; expect(memberWithNotification.newMessages[`${groupWithChat._id}`]).to.exist; + expect(memberWithNotification.notifications.find(n => { + return n.type === 'NEW_CHAT_MESSAGE' && n.data.group.id === groupWithChat._id; + })).to.exist; }); it('notifies other users of new messages for a party', async () => { @@ -443,6 +446,9 @@ describe('POST /chat', () => { expect(message.message.id).to.exist; expect(memberWithNotification.newMessages[`${group._id}`]).to.exist; + expect(memberWithNotification.notifications.find(n => { + return n.type === 'NEW_CHAT_MESSAGE' && n.data.group.id === group._id; + })).to.exist; }); context('Spam prevention', () => { diff --git a/test/api/v3/integration/chat/POST-chat_seen.test.js b/test/api/v3/integration/chat/POST-chat_seen.test.js index 8b22461a04..9207f58cf5 100644 --- a/test/api/v3/integration/chat/POST-chat_seen.test.js +++ b/test/api/v3/integration/chat/POST-chat_seen.test.js @@ -24,10 +24,13 @@ describe('POST /groups/:id/chat/seen', () => { }); it('clears new messages for a guild', async () => { + await guildMember.sync(); + const initialNotifications = guildMember.notifications.length; await guildMember.post(`/groups/${guild._id}/chat/seen`); let guildThatHasSeenChat = await guildMember.get('/user'); + expect(guildThatHasSeenChat.notifications.length).to.equal(initialNotifications - 1); expect(guildThatHasSeenChat.newMessages).to.be.empty; }); }); @@ -53,10 +56,13 @@ describe('POST /groups/:id/chat/seen', () => { }); it('clears new messages for a party', async () => { + await partyMember.sync(); + const initialNotifications = partyMember.notifications.length; await partyMember.post(`/groups/${party._id}/chat/seen`); let partyMemberThatHasSeenChat = await partyMember.get('/user'); + expect(partyMemberThatHasSeenChat.notifications.length).to.equal(initialNotifications - 1); expect(partyMemberThatHasSeenChat.newMessages).to.be.empty; }); }); diff --git a/test/api/v3/integration/groups/POST-groups_groupId_leave.js b/test/api/v3/integration/groups/POST-groups_groupId_leave.js index 403baf5da7..0ff58aee32 100644 --- a/test/api/v3/integration/groups/POST-groups_groupId_leave.js +++ b/test/api/v3/integration/groups/POST-groups_groupId_leave.js @@ -70,13 +70,21 @@ describe('POST /groups/:groupId/leave', () => { it('removes new messages for that group from user', async () => { await member.post(`/groups/${groupToLeave._id}/chat`, { message: 'Some message' }); + await sleep(0.5); + await leader.sync(); + expect(leader.notifications.find(n => { + return n.type === 'NEW_CHAT_MESSAGE' && n.data.group.id === groupToLeave._id; + })).to.exist; expect(leader.newMessages[groupToLeave._id]).to.not.be.empty; await leader.post(`/groups/${groupToLeave._id}/leave`); await leader.sync(); + expect(leader.notifications.find(n => { + return n.type === 'NEW_CHAT_MESSAGE' && n.data.group.id === groupToLeave._id; + })).to.not.exist; expect(leader.newMessages[groupToLeave._id]).to.be.empty; }); diff --git a/test/api/v3/integration/groups/POST-groups_id_removeMember.test.js b/test/api/v3/integration/groups/POST-groups_id_removeMember.test.js index e846a59290..f0cfd31ceb 100644 --- a/test/api/v3/integration/groups/POST-groups_id_removeMember.test.js +++ b/test/api/v3/integration/groups/POST-groups_id_removeMember.test.js @@ -2,6 +2,7 @@ import { generateUser, createAndPopulateGroup, translate as t, + sleep, } from '../../../../helpers/api-v3-integration.helper'; import * as email from '../../../../../website/server/libs/email'; @@ -188,13 +189,20 @@ describe('POST /groups/:groupId/removeMember/:memberId', () => { it('removes new messages from a member who is removed', async () => { await partyLeader.post(`/groups/${party._id}/chat`, { message: 'Some message' }); + await sleep(0.5); await removedMember.sync(); + expect(removedMember.notifications.find(n => { + return n.type === 'NEW_CHAT_MESSAGE' && n.data.group.id === party._id; + })).to.exist; expect(removedMember.newMessages[party._id]).to.not.be.empty; await partyLeader.post(`/groups/${party._id}/removeMember/${removedMember._id}`); await removedMember.sync(); + expect(removedMember.notifications.find(n => { + return n.type === 'NEW_CHAT_MESSAGE' && n.data.group.id === party._id; + })).to.not.exist; expect(removedMember.newMessages[party._id]).to.be.empty; }); diff --git a/test/api/v3/integration/groups/POST-groups_invite.test.js b/test/api/v3/integration/groups/POST-groups_invite.test.js index debe0d122f..3de01d0d40 100644 --- a/test/api/v3/integration/groups/POST-groups_invite.test.js +++ b/test/api/v3/integration/groups/POST-groups_invite.test.js @@ -110,6 +110,7 @@ describe('Post /groups/:groupId/invite', () => { id: group._id, name: groupName, inviter: inviter._id, + publicGuild: false, }]); await expect(userToInvite.get('/user')) @@ -127,11 +128,13 @@ describe('Post /groups/:groupId/invite', () => { id: group._id, name: groupName, inviter: inviter._id, + publicGuild: false, }, { id: group._id, name: groupName, inviter: inviter._id, + publicGuild: false, }, ]); diff --git a/test/api/v3/integration/members/POST-send_private_message.test.js b/test/api/v3/integration/members/POST-send_private_message.test.js index aaa3cb3a2c..0e484dc68f 100644 --- a/test/api/v3/integration/members/POST-send_private_message.test.js +++ b/test/api/v3/integration/members/POST-send_private_message.test.js @@ -98,6 +98,7 @@ describe('POST /members/send-private-message', () => { it('sends a private message to a user', async () => { let receiver = await generateUser(); + // const initialNotifications = receiver.notifications.length; await userToSendMessage.post('/members/send-private-message', { message: messageToSend, @@ -115,10 +116,44 @@ describe('POST /members/send-private-message', () => { return message.uuid === receiver._id && message.text === messageToSend; }); + // @TODO waiting for mobile support + // expect(updatedReceiver.notifications.length).to.equal(initialNotifications + 1); + // const notification = updatedReceiver.notifications[updatedReceiver.notifications.length - 1]; + + // expect(notification.type).to.equal('NEW_INBOX_MESSAGE'); + // expect(notification.data.messageId).to.equal(sendersMessageInReceiversInbox.id); + // expect(notification.data.excerpt).to.equal(messageToSend); + // expect(notification.data.sender.id).to.equal(updatedSender._id); + // expect(notification.data.sender.name).to.equal(updatedSender.profile.name); + expect(sendersMessageInReceiversInbox).to.exist; expect(sendersMessageInSendersInbox).to.exist; }); + // @TODO waiting for mobile support + xit('creates a notification with an excerpt if the message is too long', async () => { + let receiver = await generateUser(); + let longerMessageToSend = 'A very long message, that for sure exceeds the limit of 100 chars for the excerpt that we set to 100 chars'; + let messageExcerpt = `${longerMessageToSend.substring(0, 100)}...`; + + await userToSendMessage.post('/members/send-private-message', { + message: longerMessageToSend, + toUserId: receiver._id, + }); + + let updatedReceiver = await receiver.get('/user'); + + let sendersMessageInReceiversInbox = _.find(updatedReceiver.inbox.messages, (message) => { + return message.uuid === userToSendMessage._id && message.text === longerMessageToSend; + }); + + const notification = updatedReceiver.notifications[updatedReceiver.notifications.length - 1]; + + expect(notification.type).to.equal('NEW_INBOX_MESSAGE'); + expect(notification.data.messageId).to.equal(sendersMessageInReceiversInbox.id); + expect(notification.data.excerpt).to.equal(messageExcerpt); + }); + it('allows admin to send when sender has blocked the admin', async () => { userToSendMessage = await generateUser({ 'contributor.admin': 1, diff --git a/test/api/v3/integration/news/GET-news.test.js b/test/api/v3/integration/news/GET-news.test.js new file mode 100644 index 0000000000..b09d408706 --- /dev/null +++ b/test/api/v3/integration/news/GET-news.test.js @@ -0,0 +1,16 @@ +import { + requester, +} from '../../../../helpers/api-v3-integration.helper'; + +describe('GET /news', () => { + let api; + + beforeEach(async () => { + api = requester(); + }); + + it('returns the latest news in html format, does not require authentication', async () => { + const res = await api.get('/news'); + expect(res).to.be.a.string; + }); +}); diff --git a/test/api/v3/integration/news/POST-news_tell_me_later.test.js b/test/api/v3/integration/news/POST-news_tell_me_later.test.js new file mode 100644 index 0000000000..47444574c4 --- /dev/null +++ b/test/api/v3/integration/news/POST-news_tell_me_later.test.js @@ -0,0 +1,42 @@ +import { + generateUser, +} from '../../../../helpers/api-v3-integration.helper'; + +describe('POST /news/tell-me-later', () => { + let user; + + beforeEach(async () => { + user = await generateUser({ + 'flags.newStuff': true, + }); + }); + + it('marks new stuff as read and adds notification', async () => { + expect(user.flags.newStuff).to.equal(true); + const initialNotifications = user.notifications.length; + + await user.post('/news/tell-me-later'); + await user.sync(); + + expect(user.flags.newStuff).to.equal(false); + expect(user.notifications.length).to.equal(initialNotifications + 1); + + const notification = user.notifications[user.notifications.length - 1]; + + expect(notification.type).to.equal('NEW_STUFF'); + // should be marked as seen by default so it's not counted in the number of notifications + expect(notification.seen).to.equal(true); + expect(notification.data.title).to.be.a.string; + }); + + it('never adds two notifications', async () => { + const initialNotifications = user.notifications.length; + + await user.post('/news/tell-me-later'); + await user.post('/news/tell-me-later'); + + await user.sync(); + + expect(user.notifications.length).to.equal(initialNotifications + 1); + }); +}); diff --git a/test/api/v3/integration/notifications/POST-notifications_notificationId_read.test.js b/test/api/v3/integration/notifications/POST-notifications_notificationId_read.test.js index 02d3fd04e4..ea353aa32b 100644 --- a/test/api/v3/integration/notifications/POST-notifications_notificationId_read.test.js +++ b/test/api/v3/integration/notifications/POST-notifications_notificationId_read.test.js @@ -47,6 +47,7 @@ describe('POST /notifications/:notificationId/read', () => { id: id2, type: 'LOGIN_INCENTIVE', data: {}, + seen: false, }]); await user.sync(); diff --git a/test/api/v3/integration/notifications/POST-notifications_notificationId_see.test.js b/test/api/v3/integration/notifications/POST-notifications_notificationId_see.test.js new file mode 100644 index 0000000000..0b448f10fd --- /dev/null +++ b/test/api/v3/integration/notifications/POST-notifications_notificationId_see.test.js @@ -0,0 +1,59 @@ +import { + generateUser, + translate as t, +} from '../../../../helpers/api-v3-integration.helper'; +import { v4 as generateUUID } from 'uuid'; + +describe('POST /notifications/:notificationId/see', () => { + let user; + + before(async () => { + user = await generateUser(); + }); + + it('errors when notification is not found', async () => { + let dummyId = generateUUID(); + + await expect(user.post(`/notifications/${dummyId}/see`)).to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('messageNotificationNotFound'), + }); + }); + + it('mark a notification as seen', async () => { + expect(user.notifications.length).to.equal(0); + + const id = generateUUID(); + const id2 = generateUUID(); + + await user.update({ + notifications: [{ + id, + type: 'DROPS_ENABLED', + data: {}, + }, { + id: id2, + type: 'LOGIN_INCENTIVE', + data: {}, + }], + }); + + const userObj = await user.get('/user'); // so we can check that defaults have been applied + expect(userObj.notifications.length).to.equal(2); + expect(userObj.notifications[0].seen).to.equal(false); + + const res = await user.post(`/notifications/${id}/see`); + expect(res).to.deep.equal({ + id, + type: 'DROPS_ENABLED', + data: {}, + seen: true, + }); + + await user.sync(); + expect(user.notifications.length).to.equal(2); + expect(user.notifications[0].id).to.equal(id); + expect(user.notifications[0].seen).to.equal(true); + }); +}); diff --git a/test/api/v3/integration/notifications/POST-notifications_read.test.js b/test/api/v3/integration/notifications/POST-notifications_read.test.js index 80061e220a..55a9d3350b 100644 --- a/test/api/v3/integration/notifications/POST-notifications_read.test.js +++ b/test/api/v3/integration/notifications/POST-notifications_read.test.js @@ -4,7 +4,7 @@ import { } from '../../../../helpers/api-v3-integration.helper'; import { v4 as generateUUID } from 'uuid'; -describe('POST /notifications/:notificationId/read', () => { +describe('POST /notifications/read', () => { let user; before(async () => { @@ -57,6 +57,7 @@ describe('POST /notifications/:notificationId/read', () => { id: id2, type: 'LOGIN_INCENTIVE', data: {}, + seen: false, }]); await user.sync(); diff --git a/test/api/v3/integration/notifications/POST_notifications_see.test.js b/test/api/v3/integration/notifications/POST_notifications_see.test.js new file mode 100644 index 0000000000..d09d9bde07 --- /dev/null +++ b/test/api/v3/integration/notifications/POST_notifications_see.test.js @@ -0,0 +1,88 @@ +import { + generateUser, + translate as t, +} from '../../../../helpers/api-v3-integration.helper'; +import { v4 as generateUUID } from 'uuid'; + +describe('POST /notifications/see', () => { + let user; + + before(async () => { + user = await generateUser(); + }); + + it('errors when notification is not found', async () => { + let dummyId = generateUUID(); + + await expect(user.post('/notifications/see', { + notificationIds: [dummyId], + })).to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('messageNotificationNotFound'), + }); + }); + + it('mark multiple notifications as seen', async () => { + expect(user.notifications.length).to.equal(0); + + const id = generateUUID(); + const id2 = generateUUID(); + const id3 = generateUUID(); + + await user.update({ + notifications: [{ + id, + type: 'DROPS_ENABLED', + data: {}, + seen: false, + }, { + id: id2, + type: 'LOGIN_INCENTIVE', + data: {}, + seen: false, + }, { + id: id3, + type: 'CRON', + data: {}, + seen: false, + }], + }); + + await user.sync(); + expect(user.notifications.length).to.equal(3); + + const res = await user.post('/notifications/see', { + notificationIds: [id, id3], + }); + + expect(res).to.deep.equal([ + { + id, + type: 'DROPS_ENABLED', + data: {}, + seen: true, + }, { + id: id2, + type: 'LOGIN_INCENTIVE', + data: {}, + seen: false, + }, { + id: id3, + type: 'CRON', + data: {}, + seen: true, + }]); + + await user.sync(); + expect(user.notifications.length).to.equal(3); + expect(user.notifications[0].id).to.equal(id); + expect(user.notifications[0].seen).to.equal(true); + + expect(user.notifications[1].id).to.equal(id2); + expect(user.notifications[1].seen).to.equal(false); + + expect(user.notifications[2].id).to.equal(id3); + expect(user.notifications[2].seen).to.equal(true); + }); +}); diff --git a/test/api/v3/integration/tasks/groups/POST-group_tasks_id_needs-work_userId.test.js b/test/api/v3/integration/tasks/groups/POST-group_tasks_id_needs-work_userId.test.js new file mode 100644 index 0000000000..0be0beb720 --- /dev/null +++ b/test/api/v3/integration/tasks/groups/POST-group_tasks_id_needs-work_userId.test.js @@ -0,0 +1,189 @@ +import { + createAndPopulateGroup, + translate as t, +} from '../../../../../helpers/api-integration/v3'; +import { find } from 'lodash'; + +describe('POST /tasks/:id/needs-work/:userId', () => { + let user, guild, member, member2, task; + + function findAssignedTask (memberTask) { + return memberTask.group.id === guild._id; + } + + beforeEach(async () => { + let {group, members, groupLeader} = await createAndPopulateGroup({ + groupDetails: { + name: 'Test Guild', + type: 'guild', + }, + members: 2, + }); + + guild = group; + user = groupLeader; + member = members[0]; + member2 = members[1]; + + task = await user.post(`/tasks/group/${guild._id}`, { + text: 'test todo', + type: 'todo', + requiresApproval: true, + }); + }); + + it('errors when user is not assigned', async () => { + await expect(user.post(`/tasks/${task._id}/needs-work/${member._id}`)) + .to.eventually.be.rejected.and.to.eql({ + code: 404, + error: 'NotFound', + message: t('taskNotFound'), + }); + }); + + it('errors when user is not the group leader', async () => { + await user.post(`/tasks/${task._id}/assign/${member._id}`); + await expect(member.post(`/tasks/${task._id}/needs-work/${member._id}`)) + .to.eventually.be.rejected.and.to.eql({ + code: 401, + error: 'NotAuthorized', + message: t('onlyGroupLeaderCanEditTasks'), + }); + }); + + it('marks as task as needing more work', async () => { + const initialNotifications = member.notifications.length; + + await user.post(`/tasks/${task._id}/assign/${member._id}`); + + let memberTasks = await member.get('/tasks/user'); + let syncedTask = find(memberTasks, findAssignedTask); + + // score task to require approval + await expect(member.post(`/tasks/${syncedTask._id}/score/up`)) + .to.eventually.be.rejected.and.to.eql({ + code: 401, + error: 'NotAuthorized', + message: t('taskApprovalHasBeenRequested'), + }); + + await user.post(`/tasks/${task._id}/needs-work/${member._id}`); + + [memberTasks] = await Promise.all([member.get('/tasks/user'), member.sync()]); + syncedTask = find(memberTasks, findAssignedTask); + + // Check that the notification approval request has been removed + expect(syncedTask.group.approval.requested).to.equal(false); + expect(syncedTask.group.approval.requestedDate).to.equal(undefined); + + // Check that the notification is correct + expect(member.notifications.length).to.equal(initialNotifications + 1); + const notification = member.notifications[member.notifications.length - 1]; + expect(notification.type).to.equal('GROUP_TASK_NEEDS_WORK'); + + const taskText = syncedTask.text; + const managerName = user.profile.name; + + expect(notification.data.message).to.equal(t('taskNeedsWork', {taskText, managerName})); + + expect(notification.data.task.id).to.equal(syncedTask._id); + expect(notification.data.task.text).to.equal(taskText); + + expect(notification.data.group.id).to.equal(syncedTask.group.id); + expect(notification.data.group.name).to.equal(guild.name); + + expect(notification.data.manager.id).to.equal(user._id); + expect(notification.data.manager.name).to.equal(managerName); + + // Check that the managers' GROUP_TASK_APPROVAL notifications have been removed + await user.sync(); + + expect(user.notifications.find(n => { + n.data.taskId === syncedTask._id && n.type === 'GROUP_TASK_APPROVAL'; + })).to.equal(undefined); + }); + + it('allows a manager to mark a task as needing work', async () => { + await user.post(`/groups/${guild._id}/add-manager`, { + managerId: member2._id, + }); + await member2.post(`/tasks/${task._id}/assign/${member._id}`); + + let memberTasks = await member.get('/tasks/user'); + let syncedTask = find(memberTasks, findAssignedTask); + + // score task to require approval + await expect(member.post(`/tasks/${syncedTask._id}/score/up`)) + .to.eventually.be.rejected.and.to.eql({ + code: 401, + error: 'NotAuthorized', + message: t('taskApprovalHasBeenRequested'), + }); + + const initialNotifications = member.notifications.length; + + await member2.post(`/tasks/${task._id}/needs-work/${member._id}`); + + [memberTasks] = await Promise.all([member.get('/tasks/user'), member.sync()]); + syncedTask = find(memberTasks, findAssignedTask); + + // Check that the notification approval request has been removed + expect(syncedTask.group.approval.requested).to.equal(false); + expect(syncedTask.group.approval.requestedDate).to.equal(undefined); + + expect(member.notifications.length).to.equal(initialNotifications + 1); + const notification = member.notifications[member.notifications.length - 1]; + expect(notification.type).to.equal('GROUP_TASK_NEEDS_WORK'); + + const taskText = syncedTask.text; + const managerName = member2.profile.name; + + expect(notification.data.message).to.equal(t('taskNeedsWork', {taskText, managerName})); + + expect(notification.data.task.id).to.equal(syncedTask._id); + expect(notification.data.task.text).to.equal(taskText); + + expect(notification.data.group.id).to.equal(syncedTask.group.id); + expect(notification.data.group.name).to.equal(guild.name); + + expect(notification.data.manager.id).to.equal(member2._id); + expect(notification.data.manager.name).to.equal(managerName); + + // Check that the managers' GROUP_TASK_APPROVAL notifications have been removed + await Promise.all([user.sync(), member2.sync()]); + + expect(user.notifications.find(n => { + n.data.taskId === syncedTask._id && n.type === 'GROUP_TASK_APPROVAL'; + })).to.equal(undefined); + + expect(member2.notifications.find(n => { + n.data.taskId === syncedTask._id && n.type === 'GROUP_TASK_APPROVAL'; + })).to.equal(undefined); + }); + + it('prevents marking a task as needing work if it was already approved', async () => { + await user.post(`/groups/${guild._id}/add-manager`, { + managerId: member2._id, + }); + + await member2.post(`/tasks/${task._id}/assign/${member._id}`); + await member2.post(`/tasks/${task._id}/approve/${member._id}`); + await expect(user.post(`/tasks/${task._id}/needs-work/${member._id}`)) + .to.eventually.be.rejected.and.to.eql({ + code: 401, + error: 'NotAuthorized', + message: t('canOnlyApproveTaskOnce'), + }); + }); + + it('prevents marking a task as needing work if it is not waiting for approval', async () => { + await user.post(`/tasks/${task._id}/assign/${member._id}`); + + await expect(user.post(`/tasks/${task._id}/needs-work/${member._id}`)) + .to.eventually.be.rejected.and.to.eql({ + code: 401, + error: 'NotAuthorized', + message: t('taskApprovalWasNotRequested'), + }); + }); +}); diff --git a/test/api/v3/integration/user/GET-user_anonymized.test.js b/test/api/v3/integration/user/GET-user_anonymized.test.js index 26c8525284..dd1c9c5723 100644 --- a/test/api/v3/integration/user/GET-user_anonymized.test.js +++ b/test/api/v3/integration/user/GET-user_anonymized.test.js @@ -25,6 +25,7 @@ describe('GET /user/anonymized', () => { 'achievements.challenges': 'some', 'inbox.messages': [{ text: 'some text' }], tags: [{ name: 'some name', challenge: 'some challenge' }], + notifications: [], }); await generateHabit({ userId: user._id }); @@ -65,6 +66,7 @@ describe('GET /user/anonymized', () => { expect(returnedUser.stats.toNextLevel).to.eql(common.tnl(user.stats.lvl)); expect(returnedUser.stats.maxMP).to.eql(30); // TODO why 30? expect(returnedUser.newMessages).to.not.exist; + expect(returnedUser.notifications).to.not.exist; expect(returnedUser.profile).to.not.exist; expect(returnedUser.purchased.plan).to.not.exist; expect(returnedUser.contributor).to.not.exist; diff --git a/test/api/v3/integration/user/POST-user_open_mystery_item.test.js b/test/api/v3/integration/user/POST-user_open_mystery_item.test.js index 170a7e09ca..40ac6eca24 100644 --- a/test/api/v3/integration/user/POST-user_open_mystery_item.test.js +++ b/test/api/v3/integration/user/POST-user_open_mystery_item.test.js @@ -13,15 +13,20 @@ describe('POST /user/open-mystery-item', () => { beforeEach(async () => { user = await generateUser({ 'purchased.plan.mysteryItems': [mysteryItemKey], + notifications: [ + {type: 'NEW_MYSTERY_ITEMS', data: { items: [mysteryItemKey] }}, + ], }); }); // More tests in common code unit tests it('opens a mystery item', async () => { + expect(user.notifications.length).to.equal(1); let response = await user.post('/user/open-mystery-item'); await user.sync(); + expect(user.notifications.length).to.equal(0); expect(user.items.gear.owned[mysteryItemKey]).to.be.true; expect(response.message).to.equal(t('mysteryItemOpened')); expect(response.data.key).to.eql(mysteryItemKey); diff --git a/test/api/v3/integration/user/POST-user_read_card.test.js b/test/api/v3/integration/user/POST-user_read_card.test.js index 3b3573b6cc..bb8f388db2 100644 --- a/test/api/v3/integration/user/POST-user_read_card.test.js +++ b/test/api/v3/integration/user/POST-user_read_card.test.js @@ -26,13 +26,21 @@ describe('POST /user/read-card/:cardType', () => { await user.update({ 'items.special.greetingReceived': [true], 'flags.cardReceived': true, + notifications: [{ + type: 'CARD_RECEIVED', + data: {card: cardType}, + }], }); + await user.sync(); + expect(user.notifications.length).to.equal(1); + let response = await user.post(`/user/read-card/${cardType}`); await user.sync(); expect(response.message).to.equal(t('readCard', {cardType})); expect(user.items.special[`${cardType}Received`]).to.be.empty; expect(user.flags.cardReceived).to.be.false; + expect(user.notifications.length).to.equal(0); }); }); diff --git a/test/api/v3/unit/libs/payments.test.js b/test/api/v3/unit/libs/payments.test.js index f3e03758db..54e121c7b7 100644 --- a/test/api/v3/unit/libs/payments.test.js +++ b/test/api/v3/unit/libs/payments.test.js @@ -420,11 +420,16 @@ describe('payments/index', () => { data = { paymentMethod: 'PaymentMethod', user, sub: { key: 'basic_3mo' } }; + const oldNotificationsCount = user.notifications.length; + await api.createSubscription(data); + expect(user.notifications.find(n => n.type === 'NEW_MYSTERY_ITEMS')).to.not.be.undefined; expect(user.purchased.plan.mysteryItems).to.have.a.lengthOf(2); expect(user.purchased.plan.mysteryItems).to.include('armor_mystery_201605'); expect(user.purchased.plan.mysteryItems).to.include('head_mystery_201605'); + expect(user.notifications.length).to.equal(oldNotificationsCount + 1); + expect(user.notifications[0].type).to.equal('NEW_MYSTERY_ITEMS'); fakeClock.restore(); }); diff --git a/test/api/v3/unit/middlewares/response.js b/test/api/v3/unit/middlewares/response.js index d612b1912b..71a8bf3a22 100644 --- a/test/api/v3/unit/middlewares/response.js +++ b/test/api/v3/unit/middlewares/response.js @@ -106,6 +106,7 @@ describe('response middleware', () => { type: notification.type, id: notification.id, data: {}, + seen: false, }, ], userV: res.locals.user._v, diff --git a/test/api/v3/unit/models/group.test.js b/test/api/v3/unit/models/group.test.js index db44574a8b..18cdb23b8b 100644 --- a/test/api/v3/unit/models/group.test.js +++ b/test/api/v3/unit/models/group.test.js @@ -1011,13 +1011,6 @@ describe('Group Model', () => { expect(User.update).to.be.calledWithMatch({ 'party._id': party._id, _id: { $ne: '' }, - }, { - $set: { - [`newMessages.${party._id}`]: { - name: party.name, - value: true, - }, - }, }); }); @@ -1032,13 +1025,6 @@ describe('Group Model', () => { expect(User.update).to.be.calledWithMatch({ guilds: group._id, _id: { $ne: '' }, - }, { - $set: { - [`newMessages.${group._id}`]: { - name: group.name, - value: true, - }, - }, }); }); @@ -1049,13 +1035,6 @@ describe('Group Model', () => { expect(User.update).to.be.calledWithMatch({ 'party._id': party._id, _id: { $ne: 'user-id' }, - }, { - $set: { - [`newMessages.${party._id}`]: { - name: party.name, - value: true, - }, - }, }); }); diff --git a/test/api/v3/unit/models/user.test.js b/test/api/v3/unit/models/user.test.js index 6be822fc8d..4a7dd00b49 100644 --- a/test/api/v3/unit/models/user.test.js +++ b/test/api/v3/unit/models/user.test.js @@ -58,21 +58,23 @@ describe('User Model', () => { let userToJSON = user.toJSON(); expect(user.notifications.length).to.equal(1); - expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type']); + expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type', 'seen']); expect(userToJSON.notifications[0].type).to.equal('CRON'); expect(userToJSON.notifications[0].data).to.eql({}); + expect(userToJSON.notifications[0].seen).to.eql(false); }); - it('can add notifications with data', () => { + it('can add notifications with data and already marked as seen', () => { let user = new User(); - user.addNotification('CRON', {field: 1}); + user.addNotification('CRON', {field: 1}, true); let userToJSON = user.toJSON(); expect(user.notifications.length).to.equal(1); - expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type']); + expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type', 'seen']); expect(userToJSON.notifications[0].type).to.equal('CRON'); expect(userToJSON.notifications[0].data).to.eql({field: 1}); + expect(userToJSON.notifications[0].seen).to.eql(true); }); context('static push method', () => { @@ -86,7 +88,7 @@ describe('User Model', () => { let userToJSON = user.toJSON(); expect(user.notifications.length).to.equal(1); - expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type']); + expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type', 'seen']); expect(userToJSON.notifications[0].type).to.equal('CRON'); expect(userToJSON.notifications[0].data).to.eql({}); }); @@ -96,6 +98,7 @@ describe('User Model', () => { await user.save(); expect(User.pushNotification({_id: user._id}, 'BAD_TYPE')).to.eventually.be.rejected; + expect(User.pushNotification({_id: user._id}, 'CRON', null, 'INVALID_SEEN')).to.eventually.be.rejected; }); it('adds notifications without data for all given users via static method', async() => { @@ -109,41 +112,45 @@ describe('User Model', () => { let userToJSON = user.toJSON(); expect(user.notifications.length).to.equal(1); - expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type']); + expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type', 'seen']); expect(userToJSON.notifications[0].type).to.equal('CRON'); expect(userToJSON.notifications[0].data).to.eql({}); + expect(userToJSON.notifications[0].seen).to.eql(false); user = await User.findOne({_id: otherUser._id}).exec(); userToJSON = user.toJSON(); expect(user.notifications.length).to.equal(1); - expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type']); + expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type', 'seen']); expect(userToJSON.notifications[0].type).to.equal('CRON'); expect(userToJSON.notifications[0].data).to.eql({}); + expect(userToJSON.notifications[0].seen).to.eql(false); }); - it('adds notifications with data for all given users via static method', async() => { + it('adds notifications with data and seen status for all given users via static method', async() => { let user = new User(); let otherUser = new User(); await Bluebird.all([user.save(), otherUser.save()]); - await User.pushNotification({_id: {$in: [user._id, otherUser._id]}}, 'CRON', {field: 1}); + await User.pushNotification({_id: {$in: [user._id, otherUser._id]}}, 'CRON', {field: 1}, true); user = await User.findOne({_id: user._id}).exec(); let userToJSON = user.toJSON(); expect(user.notifications.length).to.equal(1); - expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type']); + expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type', 'seen']); expect(userToJSON.notifications[0].type).to.equal('CRON'); expect(userToJSON.notifications[0].data).to.eql({field: 1}); + expect(userToJSON.notifications[0].seen).to.eql(true); user = await User.findOne({_id: otherUser._id}).exec(); userToJSON = user.toJSON(); expect(user.notifications.length).to.equal(1); - expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type']); + expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type', 'seen']); expect(userToJSON.notifications[0].type).to.equal('CRON'); expect(userToJSON.notifications[0].data).to.eql({field: 1}); + expect(userToJSON.notifications[0].seen).to.eql(true); }); }); }); @@ -322,6 +329,65 @@ describe('User Model', () => { user = await user.save(); expect(user.achievements.beastMaster).to.not.equal(true); }); + + 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 = await user.save(); // necessary for user.isSelected to work correctly + const oldNotificationsCount = user.notifications.length; + + user.stats.points = 0; + user = await user.save(); + + expect(user.notifications.length).to.equal(oldNotificationsCount); + }); + + it('removes a notification if there are no more points to allocate', async () => { + let user = new User(); + user.stats.points = 9; + user = await user.save(); // necessary for user.isSelected to work correctly + + expect(user.notifications[0].type).to.equal('UNALLOCATED_STATS_POINTS'); + const oldNotificationsCount = user.notifications.length; + + user.stats.points = 0; + user = await user.save(); + + expect(user.notifications.length).to.equal(oldNotificationsCount - 1); + }); + + it('adds a notification if there are points to allocate', async () => { + let user = new User(); + 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 + 1); + expect(user.notifications[0].type).to.equal('UNALLOCATED_STATS_POINTS'); + expect(user.notifications[0].data.points).to.equal(9); + }); + + it('adds a notification if the points to allocate have changed', async () => { + let user = new User(); + user.stats.points = 9; + user = await user.save(); // necessary for user.isSelected to work correctly + + const oldNotificationsCount = user.notifications.length; + const oldNotificationsUUID = user.notifications[0].id; + expect(user.notifications[0].type).to.equal('UNALLOCATED_STATS_POINTS'); + expect(user.notifications[0].data.points).to.equal(9); + + user.stats.points = 11; + user = await user.save(); + + expect(user.notifications.length).to.equal(oldNotificationsCount); + expect(user.notifications[0].type).to.equal('UNALLOCATED_STATS_POINTS'); + expect(user.notifications[0].data.points).to.equal(11); + expect(user.notifications[0].id).to.not.equal(oldNotificationsUUID); + }); + }); }); context('days missed', () => { diff --git a/test/common/ops/openMysteryItem.js b/test/common/ops/openMysteryItem.js index 3766503275..f98a3048b5 100644 --- a/test/common/ops/openMysteryItem.js +++ b/test/common/ops/openMysteryItem.js @@ -29,11 +29,14 @@ describe('shared.ops.openMysteryItem', () => { let mysteryItemKey = 'eyewear_special_summerRogue'; user.purchased.plan.mysteryItems = [mysteryItemKey]; + user.notifications.push({type: 'NEW_MYSTERY_ITEMS', data: {items: [mysteryItemKey]}}); + expect(user.notifications.length).to.equal(1); let [data, message] = openMysteryItem(user); expect(user.items.gear.owned[mysteryItemKey]).to.be.true; expect(message).to.equal(i18n.t('mysteryItemOpened')); expect(data).to.eql(content.gear.flat[mysteryItemKey]); + expect(user.notifications.length).to.equal(0); }); }); diff --git a/test/common/ops/readCard.js b/test/common/ops/readCard.js index 36ef7aea14..d6d249e1f4 100644 --- a/test/common/ops/readCard.js +++ b/test/common/ops/readCard.js @@ -39,10 +39,17 @@ describe('shared.ops.readCard', () => { }); it('reads a card', () => { + user.notifications.push({ + type: 'CARD_RECEIVED', + data: {card: cardType}, + }); + const initialNotificationNuber = user.notifications.length; + let [, message] = readCard(user, {params: {cardType: 'greeting'}}); expect(message).to.equal(i18n.t('readCard', {cardType})); expect(user.items.special[`${cardType}Received`]).to.be.empty; expect(user.flags.cardReceived).to.be.false; + expect(user.notifications.length).to.equal(initialNotificationNuber - 1); }); }); diff --git a/website/client/assets/scss/button.scss b/website/client/assets/scss/button.scss index a078df469a..843bc24e62 100644 --- a/website/client/assets/scss/button.scss +++ b/website/client/assets/scss/button.scss @@ -137,3 +137,9 @@ border: 0; box-shadow: none; } + +.btn-small { + font-size: 12px; + line-height: 1.33; + padding: 4px 8px; +} diff --git a/website/client/assets/scss/dropdown.scss b/website/client/assets/scss/dropdown.scss index aed164eaf6..efe06ebf7d 100644 --- a/website/client/assets/scss/dropdown.scss +++ b/website/client/assets/scss/dropdown.scss @@ -45,6 +45,15 @@ background-color: rgba(#d5c8ff, 0.32); color: $purple-200; } + + &.dropdown-inactive { + cursor: default; + + &:active, &:hover, &.active { + background-color: inherit; + color: inherit; + } + } } .dropdown + .dropdown { diff --git a/website/client/assets/scss/icon.scss b/website/client/assets/scss/icon.scss index 0295f59daa..78a9b8b8a2 100644 --- a/website/client/assets/scss/icon.scss +++ b/website/client/assets/scss/icon.scss @@ -23,6 +23,11 @@ height: 16px; } +.icon-12 { + width: 12px; + height: 12px; +} + .icon-10 { width: 10px; height: 10px; diff --git a/website/client/assets/scss/pin.scss b/website/client/assets/scss/pin.scss index 556f8f6a13..0874cd8cc6 100644 --- a/website/client/assets/scss/pin.scss +++ b/website/client/assets/scss/pin.scss @@ -1,31 +1,26 @@ .badge-svg { - left: calc((100% - 18px) / 2); - cursor: pointer; - color: $gray-400; - background: $white; - padding: 4.5px 6px; + left: calc((100% - 18px) / 2); + cursor: pointer; + color: $gray-400; + background: $white; + padding: 4.5px 6px; - &.item-selected-badge { - background: $purple-300; - color: $white; - } + &.item-selected-badge { + background: $purple-300; + color: $white; } +} - span.badge.badge-pill.badge-item.badge-svg:not(.item-selected-badge) { - color: #a5a1ac; - } +span.badge.badge-pill.badge-item.badge-svg:not(.item-selected-badge) { + color: #a5a1ac; +} +span.badge.badge-pill.badge-item.badge-svg.hide { + display: none; +} + +.item:hover { span.badge.badge-pill.badge-item.badge-svg.hide { - display: none; + display: block; } - - .item:hover { - span.badge.badge-pill.badge-item.badge-svg.hide { - display: block; - } - } - - .icon-12 { - width: 12px; - height: 12px; - } \ No newline at end of file +} \ No newline at end of file diff --git a/website/client/assets/scss/static.scss b/website/client/assets/scss/static.scss index ef542c8b7d..258392852a 100644 --- a/website/client/assets/scss/static.scss +++ b/website/client/assets/scss/static.scss @@ -1,26 +1,32 @@ @import '~client/assets/scss/colors.scss'; -.container-fluid { +.container-fluid.static-view { margin: 5em 2em 0 2em; } -h1, h2 { - margin-top: 0.5em; - color: $purple-200; -} +.static-view { + h1, h2 { + margin-top: 0.5em; + color: $purple-200; + } -h3, h4 { - color: $purple-200; -} + h3, h4 { + color: $purple-200; + } -li, p { - font-size: 16px; -} + li, p { + font-size: 16px; + } -.media img { - margin: 1em; -} + .media img { + margin: 1em; + } -.strong { - font-weight: bold; -} + .strong { + font-weight: bold; + } + + .center-block { + margin: 0 auto 1em auto; + } +} \ No newline at end of file diff --git a/website/client/assets/scss/typography.scss b/website/client/assets/scss/typography.scss index 09b09da720..4076d98594 100644 --- a/website/client/assets/scss/typography.scss +++ b/website/client/assets/scss/typography.scss @@ -15,8 +15,27 @@ body { color: $gray-200; } -a { +a, a:not([href]):not([tabindex]) { cursor: pointer; + + &.standard-link { + color: $blue-10; + + &:hover, &:active, &:focus { + text-decoration: underline; + } + + &[disabled="disabled"] { + color: $gray-300; + text-decoration: none; + cursor: default; + } + } + + &.small-link { + font-size: 12px; + line-height: 1.33; + } } // Headers diff --git a/website/client/assets/svg/sparkles.svg b/website/client/assets/svg/sparkles.svg new file mode 100644 index 0000000000..ba7c8a81ef --- /dev/null +++ b/website/client/assets/svg/sparkles.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/website/client/assets/svg/success.svg b/website/client/assets/svg/success.svg new file mode 100644 index 0000000000..1a0049ca81 --- /dev/null +++ b/website/client/assets/svg/success.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/website/client/components/achievements/newStuff.vue b/website/client/components/achievements/newStuff.vue index 7c2d263f95..fcd8c1df76 100644 --- a/website/client/components/achievements/newStuff.vue +++ b/website/client/components/achievements/newStuff.vue @@ -1,58 +1,58 @@ - - .modal-body { - padding-top: 2em; - } + diff --git a/website/client/components/header/menu.vue b/website/client/components/header/menu.vue index cbf692741d..3d22506fb8 100644 --- a/website/client/components/header/menu.vue +++ b/website/client/components/header/menu.vue @@ -57,16 +57,16 @@ div a.dropdown-item(href="http://habitica.wikia.com/wiki/Habitica_Wiki", target='_blank') {{ $t('wiki') }} .user-menu.d-flex.align-items-center .item-with-icon(v-if="userHourglasses > 0") - .svg-icon(v-html="icons.hourglasses", v-b-tooltip.hover.bottom="$t('mysticHourglassesTooltip')") + .top-menu-icon.svg-icon(v-html="icons.hourglasses", v-b-tooltip.hover.bottom="$t('mysticHourglassesTooltip')") span {{ userHourglasses }} .item-with-icon - .svg-icon.gem(v-html="icons.gem", @click='showBuyGemsModal("gems")', v-b-tooltip.hover.bottom="$t('gems')") + .top-menu-icon.svg-icon.gem(v-html="icons.gem", @click='showBuyGemsModal("gems")', v-b-tooltip.hover.bottom="$t('gems')") span {{userGems | roundBigNumber}} .item-with-icon.gold - .svg-icon(v-html="icons.gold", v-b-tooltip.hover.bottom="$t('gold')") + .top-menu-icon.svg-icon(v-html="icons.gold", v-b-tooltip.hover.bottom="$t('gold')") span {{Math.floor(user.stats.gp * 100) / 100}} a.item-with-icon(@click="sync", v-b-tooltip.hover.bottom="$t('sync')") - .svg-icon(v-html="icons.sync") + .top-menu-icon.svg-icon(v-html="icons.sync") notification-menu.item-with-icon user-dropdown.item-with-icon @@ -236,11 +236,11 @@ div margin-right: 24px; } - &:hover /deep/ .svg-icon { + &:hover /deep/ .top-menu-icon.svg-icon { color: $white; } - & /deep/ .svg-icon { + & /deep/ .top-menu-icon.svg-icon { color: $header-color; vertical-align: bottom; display: inline-block; diff --git a/website/client/components/header/messageCount.vue b/website/client/components/header/messageCount.vue index 7163808e3e..8ad65de4d1 100644 --- a/website/client/components/header/messageCount.vue +++ b/website/client/components/header/messageCount.vue @@ -1,5 +1,7 @@ \ No newline at end of file diff --git a/website/client/components/header/notifications/base.vue b/website/client/components/header/notifications/base.vue new file mode 100644 index 0000000000..fc6bcbaa68 --- /dev/null +++ b/website/client/components/header/notifications/base.vue @@ -0,0 +1,162 @@ + + + + + + + \ No newline at end of file diff --git a/website/client/components/header/notifications/cardReceived.vue b/website/client/components/header/notifications/cardReceived.vue new file mode 100644 index 0000000000..0762f6e09a --- /dev/null +++ b/website/client/components/header/notifications/cardReceived.vue @@ -0,0 +1,35 @@ + + + \ No newline at end of file diff --git a/website/client/components/header/notifications/challengeInvitation.vue b/website/client/components/header/notifications/challengeInvitation.vue new file mode 100644 index 0000000000..97083da552 --- /dev/null +++ b/website/client/components/header/notifications/challengeInvitation.vue @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/website/client/components/header/notifications/groupTaskApproval.vue b/website/client/components/header/notifications/groupTaskApproval.vue new file mode 100644 index 0000000000..305d1bd799 --- /dev/null +++ b/website/client/components/header/notifications/groupTaskApproval.vue @@ -0,0 +1,72 @@ + + + \ No newline at end of file diff --git a/website/client/components/header/notifications/groupTaskApproved.vue b/website/client/components/header/notifications/groupTaskApproved.vue new file mode 100644 index 0000000000..35982f3ef9 --- /dev/null +++ b/website/client/components/header/notifications/groupTaskApproved.vue @@ -0,0 +1,27 @@ + + + \ No newline at end of file diff --git a/website/client/components/header/notifications/groupTaskNeedsWork.vue b/website/client/components/header/notifications/groupTaskNeedsWork.vue new file mode 100644 index 0000000000..068e479f72 --- /dev/null +++ b/website/client/components/header/notifications/groupTaskNeedsWork.vue @@ -0,0 +1,27 @@ + + + \ No newline at end of file diff --git a/website/client/components/header/notifications/guildInvitation.vue b/website/client/components/header/notifications/guildInvitation.vue new file mode 100644 index 0000000000..6920e36392 --- /dev/null +++ b/website/client/components/header/notifications/guildInvitation.vue @@ -0,0 +1,64 @@ + + + \ No newline at end of file diff --git a/website/client/components/header/notifications/newChatMessage.vue b/website/client/components/header/notifications/newChatMessage.vue new file mode 100644 index 0000000000..42c86248a4 --- /dev/null +++ b/website/client/components/header/notifications/newChatMessage.vue @@ -0,0 +1,46 @@ + + + \ No newline at end of file diff --git a/website/client/components/header/notifications/newInboxMessage.vue b/website/client/components/header/notifications/newInboxMessage.vue new file mode 100644 index 0000000000..05f76b2f15 --- /dev/null +++ b/website/client/components/header/notifications/newInboxMessage.vue @@ -0,0 +1,28 @@ + + + \ No newline at end of file diff --git a/website/client/components/header/notifications/newMysteryItems.vue b/website/client/components/header/notifications/newMysteryItems.vue new file mode 100644 index 0000000000..763b848b11 --- /dev/null +++ b/website/client/components/header/notifications/newMysteryItems.vue @@ -0,0 +1,33 @@ + + + \ No newline at end of file diff --git a/website/client/components/header/notifications/newStuff.vue b/website/client/components/header/notifications/newStuff.vue new file mode 100644 index 0000000000..a479748413 --- /dev/null +++ b/website/client/components/header/notifications/newStuff.vue @@ -0,0 +1,29 @@ + + + \ No newline at end of file diff --git a/website/client/components/header/notifications/partyInvitation.vue b/website/client/components/header/notifications/partyInvitation.vue new file mode 100644 index 0000000000..a93d277970 --- /dev/null +++ b/website/client/components/header/notifications/partyInvitation.vue @@ -0,0 +1,42 @@ + + + \ No newline at end of file diff --git a/website/client/components/header/notifications/questInvitation.vue b/website/client/components/header/notifications/questInvitation.vue new file mode 100644 index 0000000000..ce8e6b9106 --- /dev/null +++ b/website/client/components/header/notifications/questInvitation.vue @@ -0,0 +1,63 @@ + + + + + \ No newline at end of file diff --git a/website/client/components/header/notifications/unallocatedStatsPoints.vue b/website/client/components/header/notifications/unallocatedStatsPoints.vue new file mode 100644 index 0000000000..8841750fbb --- /dev/null +++ b/website/client/components/header/notifications/unallocatedStatsPoints.vue @@ -0,0 +1,45 @@ + + + + + \ No newline at end of file diff --git a/website/client/components/header/notificationsDropdown.vue b/website/client/components/header/notificationsDropdown.vue index 2b29937a91..a2849fa0bb 100644 --- a/website/client/components/header/notificationsDropdown.vue +++ b/website/client/components/header/notificationsDropdown.vue @@ -1,289 +1,254 @@ diff --git a/website/client/components/static/home.vue b/website/client/components/static/home.vue index 8f20858108..2273ed6884 100644 --- a/website/client/components/static/home.vue +++ b/website/client/components/static/home.vue @@ -1,5 +1,5 @@ + + diff --git a/website/client/components/static/overview.vue b/website/client/components/static/overview.vue index c6698d7865..cfd14c700b 100644 --- a/website/client/components/static/overview.vue +++ b/website/client/components/static/overview.vue @@ -1,5 +1,5 @@ - +