diff --git a/habitica-images b/habitica-images index 0c48d4c818..69724d88ef 160000 --- a/habitica-images +++ b/habitica-images @@ -1 +1 @@ -Subproject commit 0c48d4c81852b7a4b0a9b7f536ec75ba399fa044 +Subproject commit 69724d88ef6a4be85104fb08ae1e3d0a3f649765 diff --git a/migrations/archive/2023/20230731_naming_day.js b/migrations/archive/2023/20230731_naming_day.js new file mode 100644 index 0000000000..58ccfbb728 --- /dev/null +++ b/migrations/archive/2023/20230731_naming_day.js @@ -0,0 +1,155 @@ +/* eslint-disable no-console */ +const MIGRATION_NAME = '20230731_naming_day'; +import { v4 as uuid } from 'uuid'; + +import { model as User } from '../../../website/server/models/user'; + +const progressCount = 1000; +let count = 0; + +async function updateUser (user) { + count++; + + let set; + let push; + const inc = { + 'items.food.Cake_Base': 1, + 'items.food.Cake_CottonCandyBlue': 1, + 'items.food.Cake_CottonCandyPink': 1, + 'items.food.Cake_Desert': 1, + 'items.food.Cake_Golden': 1, + 'items.food.Cake_Red': 1, + 'items.food.Cake_Shade': 1, + 'items.food.Cake_Skeleton': 1, + 'items.food.Cake_White': 1, + 'items.food.Cake_Zombie': 1, + 'achievements.habiticaDays': 1, + }; + + if (user && user.items && user.items.gear && user.items.gear.owned && typeof user.items.gear.owned.back_special_namingDay2020 !== 'undefined') { + set = { migration: MIGRATION_NAME }; + push = { + notifications: { + type: 'ITEM_RECEIVED', + data: { + icon: 'notif_namingDay_cake', + title: 'Happy Naming Day!', + text: 'To celebrate the day we became Habitica, we’ve awarded you some cake!', + destination: '/inventory/items', + }, + seen: false, + }, + }; + } else if (user && user.items && user.items.gear && user.items.gear.owned && typeof user.items.gear.owned.body_special_namingDay2018 !== 'undefined') { + set = { migration: MIGRATION_NAME, 'items.gear.owned.back_special_namingDay2020': true }; + push = { + notifications: { + type: 'ITEM_RECEIVED', + data: { + icon: 'notif_namingDay_back', + title: 'Happy Naming Day!', + text: 'To celebrate the day we became Habitica, we’ve awarded you a Royal Purple Gryphon Tail and cake!', + destination: '/inventory/equipment', + }, + seen: false, + }, + }; + } else if (user && user.items && user.items.gear && user.items.gear.owned && typeof user.items.gear.owned.head_special_namingDay2017 !== 'undefined') { + set = { migration: MIGRATION_NAME, 'items.gear.owned.body_special_namingDay2018': true }; + push = { + notifications: { + type: 'ITEM_RECEIVED', + data: { + icon: 'notif_namingDay_body', + title: 'Happy Naming Day!', + text: 'To celebrate the day we became Habitica, we’ve awarded you a Royal Purple Gryphon Cloak and cake!', + destination: '/inventory/equipment', + }, + seen: false, + }, + }; + } else if (user && user.items && user.items.pets && typeof user.items.pets['Gryphon-RoyalPurple'] !== 'undefined') { + set = { migration: MIGRATION_NAME, 'items.gear.owned.head_special_namingDay2017': true }; + push = { + notifications: { + type: 'ITEM_RECEIVED', + data: { + icon: 'notif_namingDay_head', + title: 'Happy Naming Day!', + text: 'To celebrate the day we became Habitica, we’ve awarded you a Royal Purple Gryphon Helm and cake!', + destination: '/inventory/equipment', + }, + seen: false, + }, + }; + } else if (user && user.items && user.items.mounts && typeof user.items.mounts['Gryphon-RoyalPurple'] !== 'undefined') { + set = { migration: MIGRATION_NAME, 'items.pets.Gryphon-RoyalPurple': 5 }; + push = { + notifications: { + type: 'ITEM_RECEIVED', + data: { + icon: 'notif_namingDay_pet', + title: 'Happy Naming Day!', + text: 'To celebrate the day we became Habitica, we’ve awarded you a Royal Purple Gryphon Pet and cake!', + destination: '/inventory/stable', + }, + seen: false, + }, + }; + } else { + set = { migration: MIGRATION_NAME, 'items.mounts.Gryphon-RoyalPurple': true }; + push = { + notifications: { + type: 'ITEM_RECEIVED', + data: { + icon: 'notif_namingDay_mount', + title: 'Happy Naming Day!', + text: 'To celebrate the day we became Habitica, we’ve awarded you a Royal Purple Gryphon Mount and cake!', + destination: '/inventory/stable', + }, + seen: false, + }, + }; + } + + if (count % progressCount === 0) console.warn(`${count} ${user._id}`); + + if (push) { + return await user.updateOne({ $set: set, $inc: inc, $push: push }).exec(); + } else { + return await user.updateOne({ $set: set, $inc: inc }).exec(); + } +} + +export default async function processUsers () { + let query = { + migration: { $ne: MIGRATION_NAME }, + 'auth.timestamps.loggedin': { $gt: new Date('2023-07-01') }, + }; + + const fields = { + _id: 1, + items: 1, + }; + + while (true) { // eslint-disable-line no-constant-condition + const users = await User // eslint-disable-line no-await-in-loop + .find(query) + .limit(250) + .sort({_id: 1}) + .select(fields) + .exec(); + + if (users.length === 0) { + console.warn('All appropriate users found and modified.'); + console.warn(`\n${count} users processed\n`); + break; + } else { + query._id = { + $gt: users[users.length - 1]._id, + }; + } + + await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop + } +}; diff --git a/migrations/archive/2023/20230808_guild_gems.js b/migrations/archive/2023/20230808_guild_gems.js new file mode 100644 index 0000000000..49b880ea5b --- /dev/null +++ b/migrations/archive/2023/20230808_guild_gems.js @@ -0,0 +1,72 @@ +/* eslint-disable no-console */ +import { model as User } from '../../../website/server/models/user'; +import { model as Group } from '../../../website/server/models/group'; + +const guildsPerRun = 500; +const progressCount = 1000; +const guildsQuery = { + type: 'guild', +}; + +let count = 0; +async function updateGroup (guild) { + count++; + if (count % progressCount === 0) { + console.warn(`${count} ${guild._id}`); + } + + if (guild.hasActiveGroupPlan()) { + return console.warn(`Guild ${guild._id} is active Group Plan`); + } + + const leader = await User + .findOne({ _id: guild.leader }) + .select({ _id: true }) + .exec(); + + if (!leader) { + return console.warn(`Leader not found for Guild ${guild._id}`); + } + + if (guild.balance > 0) { + await leader.updateBalance( + guild.balance, + 'create_guild', + '', + `Guild Bank refund for ${guild.name} (${guild._id})`, + ); + } + + return guild.updateOne({ $set: { balance: 0 } }).exec(); +} + +export default async function processGroups () { + const guildFields = { + _id: 1, + balance: 1, + leader: 1, + name: 1, + purchased: 1, + }; + + while (true) { // eslint-disable-line no-constant-condition + const foundGroups = await Group // eslint-disable-line no-await-in-loop + .find(guildsQuery) + .limit(guildsPerRun) + .sort({ _id: 1 }) + .select(guildFields) + .exec(); + + if (foundGroups.length === 0) { + console.warn('All appropriate Guilds found and modified.'); + console.warn(`\n${count} Guilds processed\n`); + break; + } else { + guildsQuery._id = { + $gt: foundGroups[foundGroups.length - 1], + }; + } + + await Promise.all(foundGroups.map(guild => updateGroup(guild))); // eslint-disable-line no-await-in-loop + } +}; diff --git a/migrations/archive/2023/20230808_guild_gems_fix.js b/migrations/archive/2023/20230808_guild_gems_fix.js new file mode 100644 index 0000000000..e51bfc3086 --- /dev/null +++ b/migrations/archive/2023/20230808_guild_gems_fix.js @@ -0,0 +1,62 @@ +/* eslint-disable no-console */ +import { model as User } from '../../../website/server/models/user'; +import { TransactionModel as Transaction } from '../../../website/server/models/transaction'; + +const transactionsPerRun = 500; +const progressCount = 1000; +const transactionsQuery = { + transactionType: 'create_guild', + amount: { $gt: 0 }, +}; + +let count = 0; +async function updateTransaction (transaction) { + count++; + if (count % progressCount === 0) { + console.warn(`${count} ${transaction._id}`); + } + + const leader = await User + .findOne({ _id: transaction.userId }) + .select({ _id: true }) + .exec(); + + if (!leader) { + return console.warn(`User not found for transaction ${transaction._id}`); + } + + return leader.updateOne( + { $inc: { balance: transaction.amount }}, + ).exec(); +} + +export default async function processTransactions () { + const transactionFields = { + _id: 1, + userId: 1, + currency: 1, + amount: 1, + }; + + while (true) { // eslint-disable-line no-constant-condition + const foundTransactions = await Transaction // eslint-disable-line no-await-in-loop + .find(transactionsQuery) + .limit(transactionsPerRun) + .sort({ _id: 1 }) + .select(transactionFields) + .lean() + .exec(); + + if (foundTransactions.length === 0) { + console.warn('All appropriate transactions found and modified.'); + console.warn(`\n${count} transactions processed\n`); + break; + } else { + transactionsQuery._id = { + $gt: foundTransactions[foundTransactions.length - 1], + }; + } + + await Promise.all(foundTransactions.map(txn => updateTransaction(txn))); // eslint-disable-line no-await-in-loop + } +}; diff --git a/migrations/archive/2023/20230808_veteran_pet_ladder.js b/migrations/archive/2023/20230808_veteran_pet_ladder.js new file mode 100644 index 0000000000..fa64363cff --- /dev/null +++ b/migrations/archive/2023/20230808_veteran_pet_ladder.js @@ -0,0 +1,144 @@ +/* eslint-disable no-console */ +const MIGRATION_NAME = '20230808_veteran_pet_ladder'; +import { model as User } from '../../../website/server/models/user'; + +const progressCount = 1000; +let count = 0; + +async function updateUser (user) { + count++; + + const set = {}; + let push = { notifications: { $each: [] }}; + + set.migration = MIGRATION_NAME; + if (user.items.pets['Fox-Veteran']) { + set['items.pets.Dragon-Veteran'] = 5; + push.notifications.$each.push({ + type: 'ITEM_RECEIVED', + data: { + icon: 'icon_pet_veteran_dragon', + title: 'You’ve received a Veteran Pet!', + text: 'To commemorate being here for a new era of Habitica, we’ve awarded you a Veteran Dragon.', + destination: '/inventory/stable', + }, + seen: false, + }); + } else if (user.items.pets['Bear-Veteran']) { + set['items.pets.Fox-Veteran'] = 5; + push.notifications.$each.push({ + type: 'ITEM_RECEIVED', + data: { + icon: 'icon_pet_veteran_fox', + title: 'You’ve received a Veteran Pet!', + text: 'To commemorate being here for a new era of Habitica, we’ve awarded you a Veteran Fox.', + destination: '/inventory/stable', + }, + seen: false, + }); + } else if (user.items.pets['Lion-Veteran']) { + set['items.pets.Bear-Veteran'] = 5; + push.notifications.$each.push({ + type: 'ITEM_RECEIVED', + data: { + icon: 'icon_pet_veteran_bear', + title: 'You’ve received a Veteran Pet!', + text: 'To commemorate being here for a new era of Habitica, we’ve awarded you a Veteran Bear.', + destination: '/inventory/stable', + }, + seen: false, + }); + } else if (user.items.pets['Tiger-Veteran']) { + set['items.pets.Lion-Veteran'] = 5; + push.notifications.$each.push({ + type: 'ITEM_RECEIVED', + data: { + icon: 'icon_pet_veteran_lion', + title: 'You’ve received a Veteran Pet!', + text: 'To commemorate being here for a new era of Habitica, we’ve awarded you a Veteran Lion.', + destination: '/inventory/stable', + }, + seen: false, + }); + } else if (user.items.pets['Wolf-Veteran']) { + set['items.pets.Tiger-Veteran'] = 5; + push.notifications.$each.push({ + type: 'ITEM_RECEIVED', + data: { + icon: 'icon_pet_veteran_tiger', + title: 'You’ve received a Veteran Pet!', + text: 'To commemorate being here for a new era of Habitica, we’ve awarded you a Veteran Tiger.', + destination: '/inventory/stable', + }, + seen: false, + }); + } else { + set['items.pets.Wolf-Veteran'] = 5; + push.notifications.$each.push({ + type: 'ITEM_RECEIVED', + data: { + icon: 'icon_pet_veteran_wolf', + title: 'You’ve received a Veteran Pet!', + text: 'To commemorate being here for a new era of Habitica, we’ve awarded you a Veteran Wolf.', + destination: '/inventory/stable', + }, + seen: false, + }); + } + + if (user.contributor.level > 0) { + set['items.gear.owned.armor_special_heroicTunic'] = true; + set['items.gear.owned.back_special_heroicAureole'] = true; + set['items.gear.owned.headAccessory_special_heroicCirclet'] = true; + push.notifications.$each.push({ + type: 'ITEM_RECEIVED', + data: { + icon: 'heroic_set_icon', + title: 'You’ve received the Heroic Set!', + text: 'To commemorate your hard work as a contributor, we’ve awarded you the Heroic Circlet, Heroic Aureole, and Heroic Tunic.', + destination: '/inventory/equipment', + }, + seen: false, + }); + } + + if (count % progressCount === 0) console.warn(`${count} ${user._id}`); + + return await User.update({_id: user._id}, {$set: set, $push: push}).exec(); +} + +export default async function processUsers () { + let query = { + migration: {$ne: MIGRATION_NAME}, + // 'auth.timestamps.loggedin': { $gt: new Date('2023-07-08') }, + }; + + const fields = { + _id: 1, + items: 1, + migration: 1, + contributor: 1, + }; + + while (true) { // eslint-disable-line no-constant-condition + const users = await User // eslint-disable-line no-await-in-loop + .find(query) + .limit(250) + .sort({_id: 1}) + .select(fields) + .lean() + .exec(); + + if (users.length === 0) { + console.warn('All appropriate users found and modified.'); + console.warn(`\n${count} users processed\n`); + break; + } else { + query._id = { + $gt: users[users.length - 1], + }; + } + + await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop + } +}; diff --git a/package-lock.json b/package-lock.json index afa31e3075..a7cb0f99df 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "habitica", - "version": "4.276.2", + "version": "5.3.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -156,58 +156,14 @@ "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==", "requires": { "@babel/types": "^7.22.5" - }, - "dependencies": { - "@babel/helper-string-parser": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", - "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==" - }, - "@babel/helper-validator-identifier": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", - "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==" - }, - "@babel/types": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.5.tgz", - "integrity": "sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA==", - "requires": { - "@babel/helper-string-parser": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.5", - "to-fast-properties": "^2.0.0" - } - } } }, "@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.5.tgz", - "integrity": "sha512-m1EP3lVOPptR+2DwD125gziZNcmoNSHGmJROKoy87loWUQyJaVXDgpmruWqDARZSmtYQ+Dl25okU8+qhVzuykw==", + "version": "7.22.10", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.10.tgz", + "integrity": "sha512-Av0qubwDQxC56DoUReVDeLfMEjYYSN1nZrTUrWkXd7hpU73ymRANkbuDm3yni9npkn+RXy9nNbEJZEzXr7xrfQ==", "requires": { - "@babel/types": "^7.22.5" - }, - "dependencies": { - "@babel/helper-string-parser": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", - "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==" - }, - "@babel/helper-validator-identifier": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", - "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==" - }, - "@babel/types": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.5.tgz", - "integrity": "sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA==", - "requires": { - "@babel/helper-string-parser": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.5", - "to-fast-properties": "^2.0.0" - } - } + "@babel/types": "^7.22.10" } }, "@babel/helper-compilation-targets": { @@ -248,142 +204,116 @@ } }, "@babel/helper-create-class-features-plugin": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.5.tgz", - "integrity": "sha512-xkb58MyOYIslxu3gKmVXmjTtUPvBU4odYzbiIQbWwLKIHCsx6UGZGX6F1IznMFVnDdirseUZopzN+ZRt8Xb33Q==", + "version": "7.22.10", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.10.tgz", + "integrity": "sha512-5IBb77txKYQPpOEdUdIhBx8VrZyDCQ+H82H0+5dX1TmuscP5vJKEE3cKurjtIw/vFwzbVH48VweE78kVDBrqjA==", "requires": { "@babel/helper-annotate-as-pure": "^7.22.5", "@babel/helper-environment-visitor": "^7.22.5", "@babel/helper-function-name": "^7.22.5", "@babel/helper-member-expression-to-functions": "^7.22.5", "@babel/helper-optimise-call-expression": "^7.22.5", - "@babel/helper-replace-supers": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.9", "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.5", - "semver": "^6.3.0" + "@babel/helper-split-export-declaration": "^7.22.6", + "semver": "^6.3.1" }, "dependencies": { - "@babel/code-frame": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.5.tgz", - "integrity": "sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==", - "requires": { - "@babel/highlight": "^7.22.5" - } - }, - "@babel/helper-function-name": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz", - "integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==", - "requires": { - "@babel/template": "^7.22.5", - "@babel/types": "^7.22.5" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.5.tgz", - "integrity": "sha512-thqK5QFghPKWLhAV321lxF95yCg2K3Ob5yw+M3VHWfdia0IkPXUtoLH8x/6Fh486QUvzhb8YOWHChTVen2/PoQ==", - "requires": { - "@babel/types": "^7.22.5" - } - }, - "@babel/helper-string-parser": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", - "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==" - }, - "@babel/helper-validator-identifier": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", - "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==" - }, - "@babel/highlight": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.5.tgz", - "integrity": "sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==", - "requires": { - "@babel/helper-validator-identifier": "^7.22.5", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.5.tgz", - "integrity": "sha512-DFZMC9LJUG9PLOclRC32G63UXwzqS2koQC8dkx+PLdmt1xSePYpbT/NbsrJy8Q/muXz7o/h/d4A7Fuyixm559Q==" - }, - "@babel/template": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.5.tgz", - "integrity": "sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==", - "requires": { - "@babel/code-frame": "^7.22.5", - "@babel/parser": "^7.22.5", - "@babel/types": "^7.22.5" - } - }, - "@babel/types": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.5.tgz", - "integrity": "sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA==", - "requires": { - "@babel/helper-string-parser": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.5", - "to-fast-properties": "^2.0.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==" } } }, "@babel/helper-create-regexp-features-plugin": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.5.tgz", - "integrity": "sha512-1VpEFOIbMRaXyDeUwUfmTIxExLwQ+zkW+Bh5zXpApA3oQedBx9v/updixWxnx/bZpKw7u8VxWjb/qWpIcmPq8A==", + "version": "7.22.9", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.9.tgz", + "integrity": "sha512-+svjVa/tFwsNSG4NEy1h85+HQ5imbT92Q5/bgtS7P0GTQlP8WuFdqsiABmQouhiFGyV66oGxZFpeYHza1rNsKw==", "requires": { "@babel/helper-annotate-as-pure": "^7.22.5", "regexpu-core": "^5.3.1", - "semver": "^6.3.0" + "semver": "^6.3.1" }, "dependencies": { "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==" } } }, "@babel/helper-define-polyfill-provider": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.0.tgz", - "integrity": "sha512-RnanLx5ETe6aybRi1cO/edaRH+bNYWaryCEmjDDYyNr4wnSzyOp8T0dWipmqVHKEY3AbVKUom50AKSlj1zmKbg==", + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.2.tgz", + "integrity": "sha512-k0qnnOqHn5dK9pZpfD5XXZ9SojAITdCKRn2Lp6rnDGzIbaP0rHyMPk/4wsSxVBVz4RfN0q6VpXWP2pDGIoQ7hw==", "requires": { - "@babel/helper-compilation-targets": "^7.17.7", - "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-compilation-targets": "^7.22.6", + "@babel/helper-plugin-utils": "^7.22.5", "debug": "^4.1.1", "lodash.debounce": "^4.0.8", - "resolve": "^1.14.2", - "semver": "^6.1.2" + "resolve": "^1.14.2" }, "dependencies": { + "@babel/compat-data": { + "version": "7.22.9", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.9.tgz", + "integrity": "sha512-5UamI7xkUcJ3i9qVDS+KFDEK8/7oJ55/sJMB1Ge7IEapr7KfdfV/HErR+koZwOfd+SgtFKOKRhRakdg++DcJpQ==" + }, + "@babel/helper-compilation-targets": { + "version": "7.22.10", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.10.tgz", + "integrity": "sha512-JMSwHD4J7SLod0idLq5PKgI+6g/hLD/iuWBq08ZX49xE14VpVEojJ5rHWptpirV2j020MvypRLAXAO50igCJ5Q==", + "requires": { + "@babel/compat-data": "^7.22.9", + "@babel/helper-validator-option": "^7.22.5", + "browserslist": "^4.21.9", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + } + }, + "browserslist": { + "version": "4.21.10", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.10.tgz", + "integrity": "sha512-bipEBdZfVH5/pwrvqc+Ub0kUPVfGUhlKxbvfD+z1BDnPEO/X98ruXGA1WP5ASpAFKan7Qr6j736IacbZQuAlKQ==", + "requires": { + "caniuse-lite": "^1.0.30001517", + "electron-to-chromium": "^1.4.477", + "node-releases": "^2.0.13", + "update-browserslist-db": "^1.0.11" + } + }, + "caniuse-lite": { + "version": "1.0.30001520", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001520.tgz", + "integrity": "sha512-tahF5O9EiiTzwTUqAeFjIZbn4Dnqxzz7ktrgGlMYNLH43Ul26IgTMH/zvL3DG0lZxBYnlT04axvInszUsZULdA==" + }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "requires": { + "yallist": "^3.0.2" + } + }, "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==" + }, + "update-browserslist-db": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", + "integrity": "sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==", + "requires": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + } + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" } } }, @@ -392,34 +322,37 @@ "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz", "integrity": "sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==" }, + "@babel/helper-function-name": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz", + "integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==", + "requires": { + "@babel/template": "^7.22.5", + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-hoist-variables": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "requires": { + "@babel/types": "^7.22.5" + } + }, "@babel/helper-member-expression-to-functions": { "version": "7.22.5", "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.22.5.tgz", "integrity": "sha512-aBiH1NKMG0H2cGZqspNvsaBe6wNGjbJjuLy29aU+eDZjSbbN53BaxlpB02xm9v34pLTZ1nIQPFYn2qMZoa5BQQ==", "requires": { "@babel/types": "^7.22.5" - }, - "dependencies": { - "@babel/helper-string-parser": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", - "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==" - }, - "@babel/helper-validator-identifier": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", - "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==" - }, - "@babel/types": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.5.tgz", - "integrity": "sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA==", - "requires": { - "@babel/helper-string-parser": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.5", - "to-fast-properties": "^2.0.0" - } - } + } + }, + "@babel/helper-module-imports": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.5.tgz", + "integrity": "sha512-8Dl6+HD/cKifutF5qGd/8ZJi84QeAKh+CEe1sBzz8UayBBGg1dAIJrdHOcOM5b2MpzWL2yuotJTtGjETq0qjXg==", + "requires": { + "@babel/types": "^7.22.5" } }, "@babel/helper-module-transforms": { @@ -532,28 +465,6 @@ "integrity": "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==", "requires": { "@babel/types": "^7.22.5" - }, - "dependencies": { - "@babel/helper-string-parser": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", - "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==" - }, - "@babel/helper-validator-identifier": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", - "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==" - }, - "@babel/types": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.5.tgz", - "integrity": "sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA==", - "requires": { - "@babel/helper-string-parser": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.5", - "to-fast-properties": "^2.0.0" - } - } } }, "@babel/helper-plugin-utils": { @@ -562,114 +473,31 @@ "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==" }, "@babel/helper-remap-async-to-generator": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.5.tgz", - "integrity": "sha512-cU0Sq1Rf4Z55fgz7haOakIyM7+x/uCFwXpLPaeRzfoUtAEAuUZjZvFPjL/rk5rW693dIgn2hng1W7xbT7lWT4g==", + "version": "7.22.9", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.9.tgz", + "integrity": "sha512-8WWC4oR4Px+tr+Fp0X3RHDVfINGpF3ad1HIbrc8A77epiR6eMMc6jsgozkzT2uDiOOdoS9cLIQ+XD2XvI2WSmQ==", "requires": { "@babel/helper-annotate-as-pure": "^7.22.5", "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-wrap-function": "^7.22.5", - "@babel/types": "^7.22.5" - }, - "dependencies": { - "@babel/helper-string-parser": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", - "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==" - }, - "@babel/helper-validator-identifier": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", - "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==" - }, - "@babel/types": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.5.tgz", - "integrity": "sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA==", - "requires": { - "@babel/helper-string-parser": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.5", - "to-fast-properties": "^2.0.0" - } - } + "@babel/helper-wrap-function": "^7.22.9" } }, "@babel/helper-replace-supers": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.22.5.tgz", - "integrity": "sha512-aLdNM5I3kdI/V9xGNyKSF3X/gTyMUBohTZ+/3QdQKAA9vxIiy12E+8E2HoOP1/DjeqU+g6as35QHJNMDDYpuCg==", + "version": "7.22.9", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.22.9.tgz", + "integrity": "sha512-LJIKvvpgPOPUThdYqcX6IXRuIcTkcAub0IaDRGCZH0p5GPUp7PhRU9QVgFcDDd51BaPkk77ZjqFwh6DZTAEmGg==", "requires": { "@babel/helper-environment-visitor": "^7.22.5", "@babel/helper-member-expression-to-functions": "^7.22.5", - "@babel/helper-optimise-call-expression": "^7.22.5", - "@babel/template": "^7.22.5", - "@babel/traverse": "^7.22.5", + "@babel/helper-optimise-call-expression": "^7.22.5" + } + }, + "@babel/helper-simple-access": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", + "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", + "requires": { "@babel/types": "^7.22.5" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.5.tgz", - "integrity": "sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==", - "requires": { - "@babel/highlight": "^7.22.5" - } - }, - "@babel/helper-string-parser": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", - "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==" - }, - "@babel/helper-validator-identifier": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", - "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==" - }, - "@babel/highlight": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.5.tgz", - "integrity": "sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==", - "requires": { - "@babel/helper-validator-identifier": "^7.22.5", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.5.tgz", - "integrity": "sha512-DFZMC9LJUG9PLOclRC32G63UXwzqS2koQC8dkx+PLdmt1xSePYpbT/NbsrJy8Q/muXz7o/h/d4A7Fuyixm559Q==" - }, - "@babel/template": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.5.tgz", - "integrity": "sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==", - "requires": { - "@babel/code-frame": "^7.22.5", - "@babel/parser": "^7.22.5", - "@babel/types": "^7.22.5" - } - }, - "@babel/types": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.5.tgz", - "integrity": "sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA==", - "requires": { - "@babel/helper-string-parser": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.5", - "to-fast-properties": "^2.0.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - } } }, "@babel/helper-skip-transparent-expression-wrappers": { @@ -678,28 +506,14 @@ "integrity": "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==", "requires": { "@babel/types": "^7.22.5" - }, - "dependencies": { - "@babel/helper-string-parser": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", - "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==" - }, - "@babel/helper-validator-identifier": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", - "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==" - }, - "@babel/types": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.5.tgz", - "integrity": "sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA==", - "requires": { - "@babel/helper-string-parser": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.5", - "to-fast-properties": "^2.0.0" - } - } + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "requires": { + "@babel/types": "^7.22.5" } }, "@babel/helper-string-parser": { @@ -712,89 +526,19 @@ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==" }, - "@babel/helper-wrap-function": { + "@babel/helper-validator-option": { "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.22.5.tgz", - "integrity": "sha512-bYqLIBSEshYcYQyfks8ewYA8S30yaGSeRslcvKMvoUk6HHPySbxHq9YRi6ghhzEU+yhQv9bP/jXnygkStOcqZw==", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.5.tgz", + "integrity": "sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw==" + }, + "@babel/helper-wrap-function": { + "version": "7.22.10", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.22.10.tgz", + "integrity": "sha512-OnMhjWjuGYtdoO3FmsEFWvBStBAe2QOgwOLsLNDjN+aaiMD8InJk1/O3HSD8lkqTjCgg5YI34Tz15KNNA3p+nQ==", "requires": { "@babel/helper-function-name": "^7.22.5", "@babel/template": "^7.22.5", - "@babel/traverse": "^7.22.5", - "@babel/types": "^7.22.5" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.5.tgz", - "integrity": "sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==", - "requires": { - "@babel/highlight": "^7.22.5" - } - }, - "@babel/helper-function-name": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz", - "integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==", - "requires": { - "@babel/template": "^7.22.5", - "@babel/types": "^7.22.5" - } - }, - "@babel/helper-string-parser": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", - "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==" - }, - "@babel/helper-validator-identifier": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", - "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==" - }, - "@babel/highlight": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.5.tgz", - "integrity": "sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==", - "requires": { - "@babel/helper-validator-identifier": "^7.22.5", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.5.tgz", - "integrity": "sha512-DFZMC9LJUG9PLOclRC32G63UXwzqS2koQC8dkx+PLdmt1xSePYpbT/NbsrJy8Q/muXz7o/h/d4A7Fuyixm559Q==" - }, - "@babel/template": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.5.tgz", - "integrity": "sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==", - "requires": { - "@babel/code-frame": "^7.22.5", - "@babel/parser": "^7.22.5", - "@babel/types": "^7.22.5" - } - }, - "@babel/types": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.5.tgz", - "integrity": "sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA==", - "requires": { - "@babel/helper-string-parser": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.5", - "to-fast-properties": "^2.0.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - } + "@babel/types": "^7.22.10" } }, "@babel/helpers": { @@ -874,15 +618,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==" }, - "@babel/plugin-proposal-unicode-property-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz", - "integrity": "sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==", - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - } - }, "@babel/plugin-syntax-async-generators": { "version": "7.8.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", @@ -1037,13 +772,13 @@ } }, "@babel/plugin-transform-async-generator-functions": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.22.5.tgz", - "integrity": "sha512-gGOEvFzm3fWoyD5uZq7vVTD57pPJ3PczPUD/xCFGjzBpUosnklmXyKnGQbbbGs1NPNPskFex0j93yKbHt0cHyg==", + "version": "7.22.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.22.10.tgz", + "integrity": "sha512-eueE8lvKVzq5wIObKK/7dvoeKJ+xc6TvRn6aysIjS6pSCeLy7S/eVi7pEQknZqyqvzaNKdDtem8nUNTBgDVR2g==", "requires": { "@babel/helper-environment-visitor": "^7.22.5", "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-remap-async-to-generator": "^7.22.5", + "@babel/helper-remap-async-to-generator": "^7.22.9", "@babel/plugin-syntax-async-generators": "^7.8.4" } }, @@ -1055,36 +790,6 @@ "@babel/helper-module-imports": "^7.22.5", "@babel/helper-plugin-utils": "^7.22.5", "@babel/helper-remap-async-to-generator": "^7.22.5" - }, - "dependencies": { - "@babel/helper-module-imports": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.5.tgz", - "integrity": "sha512-8Dl6+HD/cKifutF5qGd/8ZJi84QeAKh+CEe1sBzz8UayBBGg1dAIJrdHOcOM5b2MpzWL2yuotJTtGjETq0qjXg==", - "requires": { - "@babel/types": "^7.22.5" - } - }, - "@babel/helper-string-parser": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", - "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==" - }, - "@babel/helper-validator-identifier": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", - "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==" - }, - "@babel/types": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.5.tgz", - "integrity": "sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA==", - "requires": { - "@babel/helper-string-parser": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.5", - "to-fast-properties": "^2.0.0" - } - } } }, "@babel/plugin-transform-block-scoped-functions": { @@ -1096,9 +801,9 @@ } }, "@babel/plugin-transform-block-scoping": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.22.5.tgz", - "integrity": "sha512-EcACl1i5fSQ6bt+YGuU/XGCeZKStLmyVGytWkpyhCLeQVA0eu6Wtiw92V+I1T/hnezUv7j74dA/Ro69gWcU+hg==", + "version": "7.22.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.22.10.tgz", + "integrity": "sha512-1+kVpGAOOI1Albt6Vse7c8pHzcZQdQKW+wJH+g8mCaszOdDVwRXa/slHPqIw+oJAJANTKDMuM2cBdV0Dg618Vg==", "requires": { "@babel/helper-plugin-utils": "^7.22.5" } @@ -1123,100 +828,80 @@ } }, "@babel/plugin-transform-classes": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.22.5.tgz", - "integrity": "sha512-2edQhLfibpWpsVBx2n/GKOz6JdGQvLruZQfGr9l1qes2KQaWswjBzhQF7UDUZMNaMMQeYnQzxwOMPsbYF7wqPQ==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.22.6.tgz", + "integrity": "sha512-58EgM6nuPNG6Py4Z3zSuu0xWu2VfodiMi72Jt5Kj2FECmaYk1RrTXA45z6KBFsu9tRgwQDwIiY4FXTt+YsSFAQ==", "requires": { "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-compilation-targets": "^7.22.5", + "@babel/helper-compilation-targets": "^7.22.6", "@babel/helper-environment-visitor": "^7.22.5", "@babel/helper-function-name": "^7.22.5", "@babel/helper-optimise-call-expression": "^7.22.5", "@babel/helper-plugin-utils": "^7.22.5", "@babel/helper-replace-supers": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", "globals": "^11.1.0" }, "dependencies": { - "@babel/code-frame": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.5.tgz", - "integrity": "sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==", + "@babel/compat-data": { + "version": "7.22.9", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.9.tgz", + "integrity": "sha512-5UamI7xkUcJ3i9qVDS+KFDEK8/7oJ55/sJMB1Ge7IEapr7KfdfV/HErR+koZwOfd+SgtFKOKRhRakdg++DcJpQ==" + }, + "@babel/helper-compilation-targets": { + "version": "7.22.10", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.10.tgz", + "integrity": "sha512-JMSwHD4J7SLod0idLq5PKgI+6g/hLD/iuWBq08ZX49xE14VpVEojJ5rHWptpirV2j020MvypRLAXAO50igCJ5Q==", "requires": { - "@babel/highlight": "^7.22.5" + "@babel/compat-data": "^7.22.9", + "@babel/helper-validator-option": "^7.22.5", + "browserslist": "^4.21.9", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" } }, - "@babel/helper-function-name": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz", - "integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==", + "browserslist": { + "version": "4.21.10", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.10.tgz", + "integrity": "sha512-bipEBdZfVH5/pwrvqc+Ub0kUPVfGUhlKxbvfD+z1BDnPEO/X98ruXGA1WP5ASpAFKan7Qr6j736IacbZQuAlKQ==", "requires": { - "@babel/template": "^7.22.5", - "@babel/types": "^7.22.5" + "caniuse-lite": "^1.0.30001517", + "electron-to-chromium": "^1.4.477", + "node-releases": "^2.0.13", + "update-browserslist-db": "^1.0.11" } }, - "@babel/helper-split-export-declaration": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.5.tgz", - "integrity": "sha512-thqK5QFghPKWLhAV321lxF95yCg2K3Ob5yw+M3VHWfdia0IkPXUtoLH8x/6Fh486QUvzhb8YOWHChTVen2/PoQ==", + "caniuse-lite": { + "version": "1.0.30001520", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001520.tgz", + "integrity": "sha512-tahF5O9EiiTzwTUqAeFjIZbn4Dnqxzz7ktrgGlMYNLH43Ul26IgTMH/zvL3DG0lZxBYnlT04axvInszUsZULdA==" + }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", "requires": { - "@babel/types": "^7.22.5" + "yallist": "^3.0.2" } }, - "@babel/helper-string-parser": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", - "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==" + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==" }, - "@babel/helper-validator-identifier": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", - "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==" - }, - "@babel/highlight": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.5.tgz", - "integrity": "sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==", + "update-browserslist-db": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", + "integrity": "sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==", "requires": { - "@babel/helper-validator-identifier": "^7.22.5", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" + "escalade": "^3.1.1", + "picocolors": "^1.0.0" } }, - "@babel/parser": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.5.tgz", - "integrity": "sha512-DFZMC9LJUG9PLOclRC32G63UXwzqS2koQC8dkx+PLdmt1xSePYpbT/NbsrJy8Q/muXz7o/h/d4A7Fuyixm559Q==" - }, - "@babel/template": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.5.tgz", - "integrity": "sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==", - "requires": { - "@babel/code-frame": "^7.22.5", - "@babel/parser": "^7.22.5", - "@babel/types": "^7.22.5" - } - }, - "@babel/types": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.5.tgz", - "integrity": "sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA==", - "requires": { - "@babel/helper-string-parser": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.5", - "to-fast-properties": "^2.0.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" } } }, @@ -1227,77 +912,12 @@ "requires": { "@babel/helper-plugin-utils": "^7.22.5", "@babel/template": "^7.22.5" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.5.tgz", - "integrity": "sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==", - "requires": { - "@babel/highlight": "^7.22.5" - } - }, - "@babel/helper-string-parser": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", - "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==" - }, - "@babel/helper-validator-identifier": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", - "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==" - }, - "@babel/highlight": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.5.tgz", - "integrity": "sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==", - "requires": { - "@babel/helper-validator-identifier": "^7.22.5", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.5.tgz", - "integrity": "sha512-DFZMC9LJUG9PLOclRC32G63UXwzqS2koQC8dkx+PLdmt1xSePYpbT/NbsrJy8Q/muXz7o/h/d4A7Fuyixm559Q==" - }, - "@babel/template": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.5.tgz", - "integrity": "sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==", - "requires": { - "@babel/code-frame": "^7.22.5", - "@babel/parser": "^7.22.5", - "@babel/types": "^7.22.5" - } - }, - "@babel/types": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.5.tgz", - "integrity": "sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA==", - "requires": { - "@babel/helper-string-parser": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.5", - "to-fast-properties": "^2.0.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - } } }, "@babel/plugin-transform-destructuring": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.22.5.tgz", - "integrity": "sha512-GfqcFuGW8vnEqTUBM7UtPd5A4q797LTvvwKxXTgRsFjoqaJiEg9deBG6kWeQYkVEL569NpnmpC0Pkr/8BLKGnQ==", + "version": "7.22.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.22.10.tgz", + "integrity": "sha512-dPJrL0VOyxqLM9sritNbMSGx/teueHF/htMKrPT7DNxccXxRDPYqlgPFFdr8u+F+qUZOkZoXue/6rL5O5GduEw==", "requires": { "@babel/helper-plugin-utils": "^7.22.5" } @@ -1362,80 +982,6 @@ "@babel/helper-compilation-targets": "^7.22.5", "@babel/helper-function-name": "^7.22.5", "@babel/helper-plugin-utils": "^7.22.5" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.5.tgz", - "integrity": "sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==", - "requires": { - "@babel/highlight": "^7.22.5" - } - }, - "@babel/helper-function-name": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz", - "integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==", - "requires": { - "@babel/template": "^7.22.5", - "@babel/types": "^7.22.5" - } - }, - "@babel/helper-string-parser": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", - "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==" - }, - "@babel/helper-validator-identifier": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", - "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==" - }, - "@babel/highlight": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.5.tgz", - "integrity": "sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==", - "requires": { - "@babel/helper-validator-identifier": "^7.22.5", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.5.tgz", - "integrity": "sha512-DFZMC9LJUG9PLOclRC32G63UXwzqS2koQC8dkx+PLdmt1xSePYpbT/NbsrJy8Q/muXz7o/h/d4A7Fuyixm559Q==" - }, - "@babel/template": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.5.tgz", - "integrity": "sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==", - "requires": { - "@babel/code-frame": "^7.22.5", - "@babel/parser": "^7.22.5", - "@babel/types": "^7.22.5" - } - }, - "@babel/types": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.5.tgz", - "integrity": "sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA==", - "requires": { - "@babel/helper-string-parser": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.5", - "to-fast-properties": "^2.0.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - } } }, "@babel/plugin-transform-json-strings": { @@ -1489,36 +1035,6 @@ "@babel/helper-module-transforms": "^7.22.5", "@babel/helper-plugin-utils": "^7.22.5", "@babel/helper-simple-access": "^7.22.5" - }, - "dependencies": { - "@babel/helper-simple-access": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", - "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", - "requires": { - "@babel/types": "^7.22.5" - } - }, - "@babel/helper-string-parser": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", - "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==" - }, - "@babel/helper-validator-identifier": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", - "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==" - }, - "@babel/types": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.5.tgz", - "integrity": "sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA==", - "requires": { - "@babel/helper-string-parser": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.5", - "to-fast-properties": "^2.0.0" - } - } } }, "@babel/plugin-transform-modules-systemjs": { @@ -1532,33 +1048,10 @@ "@babel/helper-validator-identifier": "^7.22.5" }, "dependencies": { - "@babel/helper-hoist-variables": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", - "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", - "requires": { - "@babel/types": "^7.22.5" - } - }, - "@babel/helper-string-parser": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", - "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==" - }, "@babel/helper-validator-identifier": { "version": "7.22.5", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==" - }, - "@babel/types": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.5.tgz", - "integrity": "sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA==", - "requires": { - "@babel/helper-string-parser": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.5", - "to-fast-properties": "^2.0.0" - } } } }, @@ -1637,9 +1130,9 @@ } }, "@babel/plugin-transform-optional-chaining": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.22.5.tgz", - "integrity": "sha512-AconbMKOMkyG+xCng2JogMCDcqW8wedQAqpVIL4cOSescZ7+iW8utC6YDZLMCSUIReEA733gzRSaOSXMAt/4WQ==", + "version": "7.22.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.22.10.tgz", + "integrity": "sha512-MMkQqZAZ+MGj+jGTG3OTuhKeBpNcO+0oCEbrGNEaOmiEn+1MzRyQlYsruGiU8RTK3zV6XwrVJTmwiDOyYK6J9g==", "requires": { "@babel/helper-plugin-utils": "^7.22.5", "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", @@ -1683,12 +1176,12 @@ } }, "@babel/plugin-transform-regenerator": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.22.5.tgz", - "integrity": "sha512-rR7KePOE7gfEtNTh9Qw+iO3Q/e4DEsoQ+hdvM6QUDH7JRJ5qxq5AA52ZzBWbI5i9lfNuvySgOGP8ZN7LAmaiPw==", + "version": "7.22.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.22.10.tgz", + "integrity": "sha512-F28b1mDt8KcT5bUyJc/U9nwzw6cV+UmTeRlXYIl2TNqMMJif0Jeey9/RQ3C4NOd2zp0/TRsDns9ttj2L523rsw==", "requires": { "@babel/helper-plugin-utils": "^7.22.5", - "regenerator-transform": "^0.15.1" + "regenerator-transform": "^0.15.2" } }, "@babel/plugin-transform-reserved-words": { @@ -1741,9 +1234,9 @@ } }, "@babel/plugin-transform-unicode-escapes": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.22.5.tgz", - "integrity": "sha512-biEmVg1IYB/raUO5wT1tgfacCef15Fbzhkx493D3urBI++6hpJ+RFG4SrWMn0NEZLfvilqKf3QDrRVZHo08FYg==", + "version": "7.22.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.22.10.tgz", + "integrity": "sha512-lRfaRKGZCBqDlRU3UIFovdp9c9mEvlylmpod0/OatICsSfuQ9YFthRo1tpTkGsklEefZdqlEFdY4A2dwTb6ohg==", "requires": { "@babel/helper-plugin-utils": "^7.22.5" } @@ -1776,12 +1269,12 @@ } }, "@babel/preset-env": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.22.5.tgz", - "integrity": "sha512-fj06hw89dpiZzGZtxn+QybifF07nNiZjZ7sazs2aVDcysAZVGjW7+7iFYxg6GLNM47R/thYfLdrXc+2f11Vi9A==", + "version": "7.22.10", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.22.10.tgz", + "integrity": "sha512-riHpLb1drNkpLlocmSyEg4oYJIQFeXAK/d7rI6mbD0XsvoTOOweXDmQPG/ErxsEhWk3rl3Q/3F6RFQlVFS8m0A==", "requires": { - "@babel/compat-data": "^7.22.5", - "@babel/helper-compilation-targets": "^7.22.5", + "@babel/compat-data": "^7.22.9", + "@babel/helper-compilation-targets": "^7.22.10", "@babel/helper-plugin-utils": "^7.22.5", "@babel/helper-validator-option": "^7.22.5", "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.22.5", @@ -1806,15 +1299,15 @@ "@babel/plugin-syntax-top-level-await": "^7.14.5", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", "@babel/plugin-transform-arrow-functions": "^7.22.5", - "@babel/plugin-transform-async-generator-functions": "^7.22.5", + "@babel/plugin-transform-async-generator-functions": "^7.22.10", "@babel/plugin-transform-async-to-generator": "^7.22.5", "@babel/plugin-transform-block-scoped-functions": "^7.22.5", - "@babel/plugin-transform-block-scoping": "^7.22.5", + "@babel/plugin-transform-block-scoping": "^7.22.10", "@babel/plugin-transform-class-properties": "^7.22.5", "@babel/plugin-transform-class-static-block": "^7.22.5", - "@babel/plugin-transform-classes": "^7.22.5", + "@babel/plugin-transform-classes": "^7.22.6", "@babel/plugin-transform-computed-properties": "^7.22.5", - "@babel/plugin-transform-destructuring": "^7.22.5", + "@babel/plugin-transform-destructuring": "^7.22.10", "@babel/plugin-transform-dotall-regex": "^7.22.5", "@babel/plugin-transform-duplicate-keys": "^7.22.5", "@babel/plugin-transform-dynamic-import": "^7.22.5", @@ -1837,71 +1330,99 @@ "@babel/plugin-transform-object-rest-spread": "^7.22.5", "@babel/plugin-transform-object-super": "^7.22.5", "@babel/plugin-transform-optional-catch-binding": "^7.22.5", - "@babel/plugin-transform-optional-chaining": "^7.22.5", + "@babel/plugin-transform-optional-chaining": "^7.22.10", "@babel/plugin-transform-parameters": "^7.22.5", "@babel/plugin-transform-private-methods": "^7.22.5", "@babel/plugin-transform-private-property-in-object": "^7.22.5", "@babel/plugin-transform-property-literals": "^7.22.5", - "@babel/plugin-transform-regenerator": "^7.22.5", + "@babel/plugin-transform-regenerator": "^7.22.10", "@babel/plugin-transform-reserved-words": "^7.22.5", "@babel/plugin-transform-shorthand-properties": "^7.22.5", "@babel/plugin-transform-spread": "^7.22.5", "@babel/plugin-transform-sticky-regex": "^7.22.5", "@babel/plugin-transform-template-literals": "^7.22.5", "@babel/plugin-transform-typeof-symbol": "^7.22.5", - "@babel/plugin-transform-unicode-escapes": "^7.22.5", + "@babel/plugin-transform-unicode-escapes": "^7.22.10", "@babel/plugin-transform-unicode-property-regex": "^7.22.5", "@babel/plugin-transform-unicode-regex": "^7.22.5", "@babel/plugin-transform-unicode-sets-regex": "^7.22.5", - "@babel/preset-modules": "^0.1.5", - "@babel/types": "^7.22.5", - "babel-plugin-polyfill-corejs2": "^0.4.3", - "babel-plugin-polyfill-corejs3": "^0.8.1", - "babel-plugin-polyfill-regenerator": "^0.5.0", - "core-js-compat": "^3.30.2", - "semver": "^6.3.0" + "@babel/preset-modules": "0.1.6-no-external-plugins", + "@babel/types": "^7.22.10", + "babel-plugin-polyfill-corejs2": "^0.4.5", + "babel-plugin-polyfill-corejs3": "^0.8.3", + "babel-plugin-polyfill-regenerator": "^0.5.2", + "core-js-compat": "^3.31.0", + "semver": "^6.3.1" }, "dependencies": { - "@babel/helper-string-parser": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", - "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==" + "@babel/compat-data": { + "version": "7.22.9", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.9.tgz", + "integrity": "sha512-5UamI7xkUcJ3i9qVDS+KFDEK8/7oJ55/sJMB1Ge7IEapr7KfdfV/HErR+koZwOfd+SgtFKOKRhRakdg++DcJpQ==" }, - "@babel/helper-validator-identifier": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", - "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==" - }, - "@babel/helper-validator-option": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.5.tgz", - "integrity": "sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw==" - }, - "@babel/types": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.5.tgz", - "integrity": "sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA==", + "@babel/helper-compilation-targets": { + "version": "7.22.10", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.10.tgz", + "integrity": "sha512-JMSwHD4J7SLod0idLq5PKgI+6g/hLD/iuWBq08ZX49xE14VpVEojJ5rHWptpirV2j020MvypRLAXAO50igCJ5Q==", "requires": { - "@babel/helper-string-parser": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.5", - "to-fast-properties": "^2.0.0" + "@babel/compat-data": "^7.22.9", + "@babel/helper-validator-option": "^7.22.5", + "browserslist": "^4.21.9", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + } + }, + "browserslist": { + "version": "4.21.10", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.10.tgz", + "integrity": "sha512-bipEBdZfVH5/pwrvqc+Ub0kUPVfGUhlKxbvfD+z1BDnPEO/X98ruXGA1WP5ASpAFKan7Qr6j736IacbZQuAlKQ==", + "requires": { + "caniuse-lite": "^1.0.30001517", + "electron-to-chromium": "^1.4.477", + "node-releases": "^2.0.13", + "update-browserslist-db": "^1.0.11" + } + }, + "caniuse-lite": { + "version": "1.0.30001520", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001520.tgz", + "integrity": "sha512-tahF5O9EiiTzwTUqAeFjIZbn4Dnqxzz7ktrgGlMYNLH43Ul26IgTMH/zvL3DG0lZxBYnlT04axvInszUsZULdA==" + }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "requires": { + "yallist": "^3.0.2" } }, "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==" + }, + "update-browserslist-db": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", + "integrity": "sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==", + "requires": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + } + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" } } }, "@babel/preset-modules": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz", - "integrity": "sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==", + "version": "0.1.6-no-external-plugins", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", + "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", "requires": { "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", - "@babel/plugin-transform-dotall-regex": "^7.4.4", "@babel/types": "^7.4.4", "esutils": "^2.0.2" } @@ -1924,11 +1445,18 @@ "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==" }, "@babel/runtime": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.5.tgz", - "integrity": "sha512-ecjvYlnAaZ/KVneE/OdKYBYfgXV3Ptu6zQWmgEF7vwKhQnvVS6bjMD2XYgj+SNvQ1GfK/pjgokfPkC/2CO8CuA==", + "version": "7.22.10", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.10.tgz", + "integrity": "sha512-21t/fkKLMZI4pqP2wlmsQAWnYW1PDyKyyUV4vCi+B25ydmdaYTKXPwCj0BzSUnZf4seIiYvSA3jcZ3gdsMFkLQ==", "requires": { - "regenerator-runtime": "^0.13.11" + "regenerator-runtime": "^0.14.0" + }, + "dependencies": { + "regenerator-runtime": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz", + "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==" + } } }, "@babel/template": { @@ -2094,18 +1622,19 @@ } }, "@babel/types": { - "version": "7.18.7", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.18.7.tgz", - "integrity": "sha512-QG3yxTcTIBoAcQmkCs+wAPYZhu7Dk9rXKacINfNbdJDNERTbLQbHGyVG8q/YGMPeCJRIhSY0+fTc5+xuh6WPSQ==", + "version": "7.22.10", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.10.tgz", + "integrity": "sha512-obaoigiLrlDZ7TUQln/8m4mSqIW2QFeOrCQc9r+xsaHGNoplVNYlRVpsfE8Vj35GEm2ZH4ZhrNYogs/3fj85kg==", "requires": { - "@babel/helper-validator-identifier": "^7.18.6", + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.5", "to-fast-properties": "^2.0.0" }, "dependencies": { "@babel/helper-validator-identifier": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz", - "integrity": "sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==" + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", + "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==" } } }, @@ -2381,9 +1910,9 @@ } }, "@mapbox/node-pre-gyp": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.10.tgz", - "integrity": "sha512-4ySo4CjzStuprMwk35H5pPbkymjv1SF3jGLj6rAHp/xT/RF7TL7bd9CTm1xDY49K2qF7jmR/g7k+SkLETP6opA==", + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", + "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", "requires": { "detect-libc": "^2.0.0", "https-proxy-agent": "^5.0.0", @@ -2411,9 +1940,9 @@ } }, "detect-libc": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz", - "integrity": "sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==" + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", + "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==" }, "emoji-regex": { "version": "8.0.0", @@ -2450,9 +1979,9 @@ }, "dependencies": { "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==" } } }, @@ -2476,9 +2005,9 @@ } }, "semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "requires": { "lru-cache": "^6.0.0" } @@ -2662,9 +2191,9 @@ } }, "@sinonjs/fake-timers": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.1.0.tgz", - "integrity": "sha512-w1qd368vtrwttm1PRJWPW1QHlbmHrVDGs1eBH/jZvRPUFS4MNXV9Q33EQdjOdeAxZ7O8+3wM7zxztm2nfUSyKw==", + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", "dev": true, "requires": { "@sinonjs/commons": "^3.0.0" @@ -3937,37 +3466,42 @@ } }, "babel-plugin-polyfill-corejs2": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.3.tgz", - "integrity": "sha512-bM3gHc337Dta490gg+/AseNB9L4YLHxq1nGKZZSHbhXv4aTYU2MD2cjza1Ru4S6975YLTaL1K8uJf6ukJhhmtw==", + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.5.tgz", + "integrity": "sha512-19hwUH5FKl49JEsvyTcoHakh6BE0wgXLLptIyKZ3PijHc/Ci521wygORCUCCred+E/twuqRyAkE02BAWPmsHOg==", "requires": { - "@babel/compat-data": "^7.17.7", - "@babel/helper-define-polyfill-provider": "^0.4.0", - "semver": "^6.1.1" + "@babel/compat-data": "^7.22.6", + "@babel/helper-define-polyfill-provider": "^0.4.2", + "semver": "^6.3.1" }, "dependencies": { + "@babel/compat-data": { + "version": "7.22.9", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.9.tgz", + "integrity": "sha512-5UamI7xkUcJ3i9qVDS+KFDEK8/7oJ55/sJMB1Ge7IEapr7KfdfV/HErR+koZwOfd+SgtFKOKRhRakdg++DcJpQ==" + }, "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==" } } }, "babel-plugin-polyfill-corejs3": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.1.tgz", - "integrity": "sha512-ikFrZITKg1xH6pLND8zT14UPgjKHiGLqex7rGEZCH2EvhsneJaJPemmpQaIZV5AL03II+lXylw3UmddDK8RU5Q==", + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.3.tgz", + "integrity": "sha512-z41XaniZL26WLrvjy7soabMXrfPWARN25PZoriDEiLMxAp50AUW3t35BGQUMg5xK3UrpVTtagIDklxYa+MhiNA==", "requires": { - "@babel/helper-define-polyfill-provider": "^0.4.0", - "core-js-compat": "^3.30.1" + "@babel/helper-define-polyfill-provider": "^0.4.2", + "core-js-compat": "^3.31.0" } }, "babel-plugin-polyfill-regenerator": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.0.tgz", - "integrity": "sha512-hDJtKjMLVa7Z+LwnTCxoDLQj6wdc+B8dun7ayF2fYieI6OzfuvcLMB32ihJZ4UhCBwNYGl5bg/x/P9cMdnkc2g==", + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.2.tgz", + "integrity": "sha512-tAlOptU0Xj34V1Y2PNTL4Y0FOJMDB6bZmoW39FeCQIhigGLkqu3Fj6uiXpxIf6Ij274ENdYx64y6Au+ZKlb1IA==", "requires": { - "@babel/helper-define-polyfill-provider": "^0.4.0" + "@babel/helper-define-polyfill-provider": "^0.4.2" } }, "babel-runtime": { @@ -4157,11 +3691,11 @@ } }, "bcrypt": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.0.tgz", - "integrity": "sha512-RHBS7HI5N5tEnGTmtR/pppX0mmDSBpQ4aCBsj7CEQfYXDcO74A8sIBYcJMuCsis2E81zDxeENYhv66oZwLiA+Q==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz", + "integrity": "sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==", "requires": { - "@mapbox/node-pre-gyp": "^1.0.10", + "@mapbox/node-pre-gyp": "^1.0.11", "node-addon-api": "^5.0.0" } }, @@ -5001,9 +4535,9 @@ } }, "chalk": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.2.0.tgz", - "integrity": "sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", "dev": true }, "chardet": { @@ -5622,11 +5156,38 @@ "dev": true }, "core-js-compat": { - "version": "3.30.2", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.30.2.tgz", - "integrity": "sha512-nriW1nuJjUgvkEjIot1Spwakz52V9YkYHZAQG6A1eCgC8AA1p0zngrQEP9R0+V6hji5XilWKG1Bd0YRppmGimA==", + "version": "3.32.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.32.0.tgz", + "integrity": "sha512-7a9a3D1k4UCVKnLhrgALyFcP7YCsLOQIxPd0dKjf/6GuPcgyiGP70ewWdCGrSK7evyhymi0qO4EqCmSJofDeYw==", "requires": { - "browserslist": "^4.21.5" + "browserslist": "^4.21.9" + }, + "dependencies": { + "browserslist": { + "version": "4.21.10", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.10.tgz", + "integrity": "sha512-bipEBdZfVH5/pwrvqc+Ub0kUPVfGUhlKxbvfD+z1BDnPEO/X98ruXGA1WP5ASpAFKan7Qr6j736IacbZQuAlKQ==", + "requires": { + "caniuse-lite": "^1.0.30001517", + "electron-to-chromium": "^1.4.477", + "node-releases": "^2.0.13", + "update-browserslist-db": "^1.0.11" + } + }, + "caniuse-lite": { + "version": "1.0.30001520", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001520.tgz", + "integrity": "sha512-tahF5O9EiiTzwTUqAeFjIZbn4Dnqxzz7ktrgGlMYNLH43Ul26IgTMH/zvL3DG0lZxBYnlT04axvInszUsZULdA==" + }, + "update-browserslist-db": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", + "integrity": "sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==", + "requires": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + } + } } }, "core-util-is": { @@ -6385,6 +5946,11 @@ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, + "electron-to-chromium": { + "version": "1.4.490", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.490.tgz", + "integrity": "sha512-6s7NVJz+sATdYnIwhdshx/N/9O6rvMxmhVoDSDFdj6iA45gHR8EQje70+RYsF4GeB+k0IeNSBnP7yG9ZXJFr7A==" + }, "emitter-listener": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/emitter-listener/-/emitter-listener-1.1.2.tgz", @@ -7542,6 +7108,15 @@ "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.6.tgz", "integrity": "sha512-VhXlQgj9ioXCqGstD37E/HBeqEGV/qOD/kmbVG8h5xKBYvM1L3lR1Zn4555cQ8GkYbJa8aJSipLPndE1k6zK2w==" }, + "fast-xml-parser": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.2.6.tgz", + "integrity": "sha512-Xo1qV++h/Y3Ng8dphjahnYe+rGHaaNdsYOBWL9Y9GCPKpNKilJtilvWkLcI9f9X2DoKTLsZsGYAls5+JL5jfLA==", + "optional": true, + "requires": { + "strnum": "^1.0.5" + } + }, "fastest-levenshtein": { "version": "1.0.16", "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", @@ -7866,9 +7441,9 @@ }, "dependencies": { "qs": { - "version": "6.11.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.1.tgz", - "integrity": "sha512-0wsrzgTz/kAVIeuxSjnpGC56rzYtr6JT/2BwEvMaPhFIoYa1aGO8LbzuU1R0uUYQkLpWBTOj0l/CLAJB64J6nQ==", + "version": "6.11.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz", + "integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==", "requires": { "side-channel": "^1.0.4" } @@ -7955,6 +7530,16 @@ "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", "requires": { "minipass": "^3.0.0" + }, + "dependencies": { + "minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "requires": { + "yallist": "^4.0.0" + } + } } }, "fs-mkdirp-stream": { @@ -9938,17 +9523,6 @@ "optional": true, "requires": { "fast-xml-parser": "^4.1.3" - }, - "dependencies": { - "fast-xml-parser": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.2.4.tgz", - "integrity": "sha512-fbfMDvgBNIdDJLdLOwacjFAPYt67tr31H9ZhWSm45CDAxvd0I6WTlSOUo7K2P/K5sA5JgMKG64PI3DMcaFdWpQ==", - "optional": true, - "requires": { - "strnum": "^1.0.5" - } - } } }, "is-symbol": { @@ -10346,9 +9920,9 @@ } }, "jsonwebtoken": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz", - "integrity": "sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.1.tgz", + "integrity": "sha512-K8wx7eJ5TPvEjuiVSkv167EVboBDv9PZdDoF7BgeQnBLVvZWW9clr2PsQHVJDTKaEIH5JBIwHujGcHp7GgI2eg==", "requires": { "jws": "^3.2.2", "lodash": "^4.17.21", @@ -10376,9 +9950,9 @@ } }, "semver": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz", - "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "requires": { "lru-cache": "^6.0.0" } @@ -11274,12 +10848,9 @@ "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" }, "minipass": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.4.tgz", - "integrity": "sha512-I9WPbWHCGu8W+6k1ZiGpPu0GkoKBeorkfKNuAFBNS1HNFJvke82sxvI5bzcCNpWPorkOO5QQ+zomzzwRxejXiw==", - "requires": { - "yallist": "^4.0.0" - } + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==" }, "minizlib": { "version": "2.1.2", @@ -11288,6 +10859,16 @@ "requires": { "minipass": "^3.0.0", "yallist": "^4.0.0" + }, + "dependencies": { + "minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "requires": { + "yallist": "^4.0.0" + } + } } }, "mixin-deep": { @@ -11995,9 +11576,9 @@ } }, "node-addon-api": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.0.0.tgz", - "integrity": "sha512-CvkDw2OEnme7ybCykJpVcKH+uAOLV2qLqiyla128dN9TkEWfrYmxG6C2boDe5KcNQqZF3orkqzGgOMvZ/JNekA==" + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", + "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==" }, "node-bitmap": { "version": "0.0.1", @@ -12047,6 +11628,11 @@ "request": ">=2.76.0 <3.0.0" } }, + "node-releases": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", + "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==" + }, "nodemon": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.4.tgz", @@ -13137,9 +12723,9 @@ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" }, "rate-limiter-flexible": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/rate-limiter-flexible/-/rate-limiter-flexible-2.4.1.tgz", - "integrity": "sha512-dgH4T44TzKVO9CLArNto62hJOwlWJMLUjVVr/ii0uUzZXEXthDNr7/yefW5z/1vvHAfycc1tnuiYyNJ8CTRB3g==" + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/rate-limiter-flexible/-/rate-limiter-flexible-2.4.2.tgz", + "integrity": "sha512-rMATGGOdO1suFyf/mI5LYhts71g1sbdhmd6YvdiXO2gJnd42Tt6QS4JUKJKSWVVkMtBacm6l40FR7Trjo6Iruw==" }, "raw-body": { "version": "2.5.1", @@ -13345,9 +12931,9 @@ "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" }, "regenerator-transform": { - "version": "0.15.1", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.1.tgz", - "integrity": "sha512-knzmNAcuyxV+gQCufkYcvOqX/qIIfHLv0u5x79kRxuGojfYVky1f15TzZEu2Avte8QGepvUNTnLskf8E6X6Vyg==", + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz", + "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==", "requires": { "@babel/runtime": "^7.8.4" } @@ -14157,13 +13743,13 @@ } }, "sinon": { - "version": "15.1.2", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-15.1.2.tgz", - "integrity": "sha512-uG1pU54Fis4EfYOPoEi13fmRHgZNg/u+3aReSEzHsN52Bpf+bMVfsBQS5MjouI+rTuG6UBIINlpuuO2Epr7SiA==", + "version": "15.2.0", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-15.2.0.tgz", + "integrity": "sha512-nPS85arNqwBXaIsFCkolHjGIkFo+Oxu9vbgmBJizLAhqe6P2o3Qmj3KCUoRkfhHtvgDhZdWD3risLHAUJ8npjw==", "dev": true, "requires": { "@sinonjs/commons": "^3.0.0", - "@sinonjs/fake-timers": "^10.1.0", + "@sinonjs/fake-timers": "^10.3.0", "@sinonjs/samsam": "^8.0.0", "diff": "^5.1.0", "nise": "^5.1.4", @@ -14851,9 +14437,9 @@ "integrity": "sha512-GPcQ+LDJbrcxHORTRes6Jy2sfvK2kS6hpSfI/fXhPt+spVzxF6LJ1dHLN9zIGmVaaP044YKaIatFaufENRiDoQ==" }, "superagent": { - "version": "8.0.9", - "resolved": "https://registry.npmjs.org/superagent/-/superagent-8.0.9.tgz", - "integrity": "sha512-4C7Bh5pyHTvU33KpZgwrNKh/VQnvgtCSqPRfJAUdmrtSYePVzVg4E4OzsrbkhJj9O7SO6Bnv75K/F8XVZT8YHA==", + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-8.1.2.tgz", + "integrity": "sha512-6WTxW1EB6yCxV5VFOIPQruWGHqc3yI7hEmZK6h+pyk69Lk/Ut7rLUY6W/ONF2MjBuGjvmMiIpsrVJ2vjrHlslA==", "requires": { "component-emitter": "^1.3.0", "cookiejar": "^2.1.4", @@ -14876,17 +14462,17 @@ } }, "qs": { - "version": "6.11.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.1.tgz", - "integrity": "sha512-0wsrzgTz/kAVIeuxSjnpGC56rzYtr6JT/2BwEvMaPhFIoYa1aGO8LbzuU1R0uUYQkLpWBTOj0l/CLAJB64J6nQ==", + "version": "6.11.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz", + "integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==", "requires": { "side-channel": "^1.0.4" } }, "semver": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz", - "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "requires": { "lru-cache": "^6.0.0" } @@ -14984,13 +14570,13 @@ "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==" }, "tar": { - "version": "6.1.11", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz", - "integrity": "sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==", + "version": "6.1.15", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.15.tgz", + "integrity": "sha512-/zKt9UyngnxIT/EAGYuxaMYgOIJiP81ab9ZfkILq4oNLPFX50qyYmu7jRj9qeXoxmJHjGlbH0+cm2uy1WCs10A==", "requires": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", - "minipass": "^3.0.0", + "minipass": "^5.0.0", "minizlib": "^2.1.1", "mkdirp": "^1.0.3", "yallist": "^4.0.0" @@ -16521,9 +16107,9 @@ "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==" }, "winston": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/winston/-/winston-3.9.0.tgz", - "integrity": "sha512-jW51iW/X95BCW6MMtZWr2jKQBP4hV5bIDq9QrIjfDk6Q9QuxvTKEAlpUNAzP+HYHFFCeENhph16s0zEunu4uuQ==", + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.10.0.tgz", + "integrity": "sha512-nT6SIDaE9B7ZRO0u3UvdrimG0HkB7dSTAgInQnNR2SOPJ4bvq5q79+pXLftKmP52lJGW15+H5MCK0nM9D3KB/g==", "requires": { "@colors/colors": "1.5.0", "@dabh/diagnostics": "^2.0.2", @@ -16595,9 +16181,9 @@ } }, "word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==" + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.4.tgz", + "integrity": "sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==" }, "wordwrap": { "version": "1.0.0", @@ -16685,9 +16271,9 @@ } }, "xml2js": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.0.tgz", - "integrity": "sha512-eLTh0kA8uHceqesPqSE+VvO1CDDJWMwlQfB6LuN6T8w6MaDJ8Txm8P7s5cHD0miF0V+GGTZrDQfxPZQVsur33w==", + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz", + "integrity": "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==", "requires": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" diff --git a/package.json b/package.json index 2f4b94f347..f5142dd939 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,11 @@ { "name": "habitica", "description": "A habit tracker app which treats your goals like a Role Playing Game.", - "version": "4.276.2", + "version": "5.3.0", "main": "./website/server/index.js", "dependencies": { "@babel/core": "^7.22.5", - "@babel/preset-env": "^7.22.5", + "@babel/preset-env": "^7.22.10", "@babel/register": "^7.22.5", "@google-cloud/trace-agent": "^7.1.2", "@parse/node-apn": "^5.1.3", @@ -15,7 +15,7 @@ "amplitude": "^6.0.0", "apidoc": "^0.54.0", "apple-auth": "^1.0.9", - "bcrypt": "^5.1.0", + "bcrypt": "^5.1.1", "body-parser": "^1.20.2", "bootstrap": "^4.6.0", "compression": "^1.7.4", @@ -42,7 +42,7 @@ "image-size": "^1.0.2", "in-app-purchase": "^1.11.3", "js2xmlparser": "^5.0.0", - "jsonwebtoken": "^9.0.0", + "jsonwebtoken": "^9.0.1", "jwks-rsa": "^2.1.5", "lodash": "^4.17.21", "merge-stream": "^2.0.0", @@ -61,22 +61,22 @@ "paypal-rest-sdk": "^1.8.1", "pp-ipn": "^1.1.0", "ps-tree": "^1.0.0", - "rate-limiter-flexible": "^2.4.0", + "rate-limiter-flexible": "^2.4.2", "redis": "^3.1.2", "regenerator-runtime": "^0.13.11", "remove-markdown": "^0.5.0", "rimraf": "^3.0.2", "short-uuid": "^4.2.2", "stripe": "^12.9.0", - "superagent": "^8.0.9", + "superagent": "^8.1.2", "universal-analytics": "^0.5.3", "useragent": "^2.1.9", "uuid": "^9.0.0", "validator": "^13.9.0", "vinyl-buffer": "^1.0.1", - "winston": "^3.9.0", + "winston": "^3.10.0", "winston-loggly-bulk": "^3.2.1", - "xml2js": "^0.6.0" + "xml2js": "^0.6.2" }, "private": true, "engines": { @@ -114,7 +114,7 @@ "chai": "^4.3.7", "chai-as-promised": "^7.1.1", "chai-moment": "^0.1.0", - "chalk": "^5.2.0", + "chalk": "^5.3.0", "cross-spawn": "^7.0.3", "expect.js": "^0.3.1", "istanbul": "^1.1.0-alpha.1", @@ -122,7 +122,7 @@ "monk": "^7.3.4", "require-again": "^2.0.0", "run-rs": "^0.7.7", - "sinon": "^15.1.2", + "sinon": "^15.2.0", "sinon-chai": "^3.7.0", "sinon-stub-promise": "^4.0.0" }, diff --git a/test/api/v3/integration/challenges/GET-challenges_challengeId.test.js b/test/api/v3/integration/challenges/GET-challenges_challengeId.test.js index 7f70513da8..1fe1018df0 100644 --- a/test/api/v3/integration/challenges/GET-challenges_challengeId.test.js +++ b/test/api/v3/integration/challenges/GET-challenges_challengeId.test.js @@ -16,60 +16,7 @@ describe('GET /challenges/:challengeId', () => { }); }); - context('public guild', () => { - let groupLeader; - let group; - let challenge; - let user; - - beforeEach(async () => { - user = await generateUser(); - - const populatedGroup = await createAndPopulateGroup({ - groupDetails: { type: 'guild', privacy: 'public' }, - }); - - groupLeader = populatedGroup.groupLeader; - group = populatedGroup.group; - - challenge = await generateChallenge(groupLeader, group); - await groupLeader.post(`/challenges/${challenge._id}/join`); - }); - - it('should return challenge data', async () => { - await challenge.sync(); - const chal = await user.get(`/challenges/${challenge._id}`); - expect(chal.memberCount).to.equal(challenge.memberCount); - expect(chal.name).to.equal(challenge.name); - expect(chal._id).to.equal(challenge._id); - - expect(chal.leader).to.eql({ - _id: groupLeader._id, - id: groupLeader._id, - profile: { name: groupLeader.profile.name }, - auth: { - local: { - username: groupLeader.auth.local.username, - }, - }, - flags: { - verifiedUsername: true, - }, - }); - expect(chal.group).to.eql({ - _id: group._id, - categories: [], - id: group.id, - name: group.name, - summary: group.name, - type: group.type, - privacy: group.privacy, - leader: groupLeader.id, - }); - }); - }); - - context('private guild', () => { + context('Group Plan', () => { let groupLeader; let challengeLeader; let group; @@ -84,14 +31,14 @@ describe('GET /challenges/:challengeId', () => { const populatedGroup = await createAndPopulateGroup({ groupDetails: { type: 'guild', privacy: 'private' }, members: 2, + upgradeToGroupPlan: true, }); groupLeader = populatedGroup.groupLeader; group = populatedGroup.group; members = populatedGroup.members; - challengeLeader = members[0]; // eslint-disable-line prefer-destructuring - otherMember = members[1]; // eslint-disable-line prefer-destructuring + [challengeLeader, otherMember] = members; challenge = await generateChallenge(challengeLeader, group); }); diff --git a/test/api/v3/integration/challenges/GET-challenges_challengeId_members.test.js b/test/api/v3/integration/challenges/GET-challenges_challengeId_members.test.js index 01560cf037..fe729e38f3 100644 --- a/test/api/v3/integration/challenges/GET-challenges_challengeId_members.test.js +++ b/test/api/v3/integration/challenges/GET-challenges_challengeId_members.test.js @@ -71,42 +71,18 @@ describe('GET /challenges/:challengeId/members', () => { }); }); - it('works with challenges belonging to public guild', async () => { - const leader = await generateUser({ balance: 4 }); - const group = await generateGroup(leader, { type: 'guild', privacy: 'public', name: generateUUID() }); - const challenge = await generateChallenge(leader, group); - await leader.post(`/challenges/${challenge._id}/join`); - const res = await user.get(`/challenges/${challenge._id}/members`); - expect(res[0]).to.eql({ - _id: leader._id, - id: leader._id, - profile: { name: leader.profile.name }, - auth: { - local: { - username: leader.auth.local.username, - }, - }, - flags: { - verifiedUsername: true, - }, - }); - expect(res[0]).to.have.all.keys(['_id', 'auth', 'flags', 'id', 'profile']); - expect(res[0].profile).to.have.all.keys(['name']); - }); - it('populates only some fields', async () => { - const anotherUser = await generateUser({ balance: 3 }); - const group = await generateGroup(anotherUser, { type: 'guild', privacy: 'public', name: generateUUID() }); - const challenge = await generateChallenge(anotherUser, group); - await anotherUser.post(`/challenges/${challenge._id}/join`); + const group = await generateGroup(user, { type: 'party', privacy: 'private', name: generateUUID() }); + const challenge = await generateChallenge(user, group); + await user.post(`/challenges/${challenge._id}/join`); const res = await user.get(`/challenges/${challenge._id}/members`); expect(res[0]).to.eql({ - _id: anotherUser._id, - id: anotherUser._id, - profile: { name: anotherUser.profile.name }, + _id: user._id, + id: user._id, + profile: { name: user.profile.name }, auth: { local: { - username: anotherUser.auth.local.username, + username: user.auth.local.username, }, }, flags: { diff --git a/test/api/v3/integration/challenges/GET-challenges_challengeId_members_memberId.test.js b/test/api/v3/integration/challenges/GET-challenges_challengeId_members_memberId.test.js index 03ffa8d8f4..5fe0068899 100644 --- a/test/api/v3/integration/challenges/GET-challenges_challengeId_members_memberId.test.js +++ b/test/api/v3/integration/challenges/GET-challenges_challengeId_members_memberId.test.js @@ -72,20 +72,6 @@ describe('GET /challenges/:challengeId/members/:memberId', () => { }); }); - it('works with challenges belonging to a public guild', async () => { - const groupLeader = await generateUser({ balance: 4 }); - const group = await generateGroup(groupLeader, { type: 'guild', privacy: 'public', name: generateUUID() }); - const challenge = await generateChallenge(groupLeader, group); - await groupLeader.post(`/challenges/${challenge._id}/join`); - const taskText = 'Test Text'; - await groupLeader.post(`/tasks/challenge/${challenge._id}`, [{ type: 'habit', text: taskText }]); - - const memberProgress = await user.get(`/challenges/${challenge._id}/members/${groupLeader._id}`); - expect(memberProgress).to.have.all.keys(['_id', 'auth', 'flags', 'id', 'profile', 'tasks']); - expect(memberProgress.profile).to.have.all.keys(['name']); - expect(memberProgress.tasks.length).to.equal(1); - }); - it('returns the member tasks for the challenges', async () => { const group = await generateGroup(user, { type: 'party', name: generateUUID() }); const challenge = await generateChallenge(user, group); diff --git a/test/api/v3/integration/challenges/GET-challenges_group_groupid.test.js b/test/api/v3/integration/challenges/GET-challenges_group_groupid.test.js index fc26d4ce0d..8fc956a544 100644 --- a/test/api/v3/integration/challenges/GET-challenges_group_groupid.test.js +++ b/test/api/v3/integration/challenges/GET-challenges_group_groupid.test.js @@ -7,117 +7,7 @@ import { import { TAVERN_ID } from '../../../../../website/common/script/constants'; describe('GET challenges/groups/:groupId', () => { - context('Public Guild', () => { - let publicGuild; let user; let nonMember; let challenge; let - challenge2; - - before(async () => { - const { group, groupLeader } = await createAndPopulateGroup({ - groupDetails: { - name: 'TestGuild', - type: 'guild', - privacy: 'public', - }, - }); - - publicGuild = group; - user = groupLeader; - - nonMember = await generateUser(); - - challenge = await generateChallenge(user, group); - await user.post(`/challenges/${challenge._id}/join`); - challenge2 = await generateChallenge(user, group); - await user.post(`/challenges/${challenge2._id}/join`); - }); - - it('should return group challenges for non member with populated leader', async () => { - const challenges = await nonMember.get(`/challenges/groups/${publicGuild._id}`); - - const foundChallenge1 = _.find(challenges, { _id: challenge._id }); - expect(foundChallenge1).to.exist; - expect(foundChallenge1.leader).to.eql({ - _id: publicGuild.leader._id, - id: publicGuild.leader._id, - profile: { name: user.profile.name }, - auth: { - local: { - username: user.auth.local.username, - }, - }, - flags: { - verifiedUsername: true, - }, - }); - const foundChallenge2 = _.find(challenges, { _id: challenge2._id }); - expect(foundChallenge2).to.exist; - expect(foundChallenge2.leader).to.eql({ - _id: publicGuild.leader._id, - id: publicGuild.leader._id, - profile: { name: user.profile.name }, - auth: { - local: { - username: user.auth.local.username, - }, - }, - flags: { - verifiedUsername: true, - }, - }); - }); - - it('should return group challenges for member with populated leader', async () => { - const challenges = await user.get(`/challenges/groups/${publicGuild._id}`); - - const foundChallenge1 = _.find(challenges, { _id: challenge._id }); - expect(foundChallenge1).to.exist; - expect(foundChallenge1.leader).to.eql({ - _id: publicGuild.leader._id, - id: publicGuild.leader._id, - profile: { name: user.profile.name }, - auth: { - local: { - username: user.auth.local.username, - }, - }, - flags: { - verifiedUsername: true, - }, - }); - const foundChallenge2 = _.find(challenges, { _id: challenge2._id }); - expect(foundChallenge2).to.exist; - expect(foundChallenge2.leader).to.eql({ - _id: publicGuild.leader._id, - id: publicGuild.leader._id, - profile: { name: user.profile.name }, - auth: { - local: { - username: user.auth.local.username, - }, - }, - flags: { - verifiedUsername: true, - }, - }); - }); - - it('should return newest challenges first', async () => { - let challenges = await user.get(`/challenges/groups/${publicGuild._id}`); - - let foundChallengeIndex = _.findIndex(challenges, { _id: challenge2._id }); - expect(foundChallengeIndex).to.eql(0); - - const newChallenge = await generateChallenge(user, publicGuild); - await user.post(`/challenges/${newChallenge._id}/join`); - - challenges = await user.get(`/challenges/groups/${publicGuild._id}`); - - foundChallengeIndex = _.findIndex(challenges, { _id: newChallenge._id }); - expect(foundChallengeIndex).to.eql(0); - }); - }); - - context('Private Guild', () => { + context('Group Plan', () => { let privateGuild; let user; let nonMember; let challenge; let challenge2; @@ -128,6 +18,7 @@ describe('GET challenges/groups/:groupId', () => { type: 'guild', privacy: 'private', }, + upgradeToGroupPlan: true, }); privateGuild = group; @@ -186,68 +77,6 @@ describe('GET challenges/groups/:groupId', () => { }); }); - context('official challenge is present', () => { - let publicGuild; let user; let officialChallenge; let unofficialChallenges; - - before(async () => { - const { group, groupLeader } = await createAndPopulateGroup({ - groupDetails: { - name: 'TestGuild', - type: 'guild', - privacy: 'public', - }, - }); - - user = groupLeader; - publicGuild = group; - - await user.update({ - 'permissions.challengeAdmin': true, - }); - - officialChallenge = await generateChallenge(user, group, { - categories: [{ - name: 'habitica_official', - slug: 'habitica_official', - }], - }); - await user.post(`/challenges/${officialChallenge._id}/join`); - - // We add 10 extra challenges to test whether the official challenge - // (the oldest) makes it to the front page. - unofficialChallenges = []; - for (let i = 0; i < 10; i += 1) { - const challenge = await generateChallenge(user, group); // eslint-disable-line - await user.post(`/challenges/${challenge._id}/join`); // eslint-disable-line - unofficialChallenges.push(challenge); - } - }); - - it('should return official challenges first', async () => { - const challenges = await user.get(`/challenges/groups/${publicGuild._id}`); - - const foundChallengeIndex = _.findIndex(challenges, { _id: officialChallenge._id }); - expect(foundChallengeIndex).to.eql(0); - }); - - it('should return newest challenges first, after official ones', async () => { - let challenges = await user.get(`/challenges/groups/${publicGuild._id}`); - - unofficialChallenges.forEach((chal, index) => { - const foundChallengeIndex = _.findIndex(challenges, { _id: chal._id }); - expect(foundChallengeIndex).to.eql(10 - index); - }); - - const newChallenge = await generateChallenge(user, publicGuild); - await user.post(`/challenges/${newChallenge._id}/join`); - - challenges = await user.get(`/challenges/groups/${publicGuild._id}`); - - const foundChallengeIndex = _.findIndex(challenges, { _id: newChallenge._id }); - expect(foundChallengeIndex).to.eql(1); - }); - }); - context('Party', () => { let party; let user; let nonMember; let challenge; let challenge2; @@ -401,7 +230,7 @@ describe('GET challenges/groups/:groupId', () => { }); }); - it('should return tavern challenges using ID "habitrpg', async () => { + it('should return tavern challenges using ID "habitrpg"', async () => { const challenges = await user.get('/challenges/groups/habitrpg'); const foundChallenge1 = _.find(challenges, { _id: challenge._id }); @@ -435,5 +264,58 @@ describe('GET challenges/groups/:groupId', () => { }, }); }); + + context('official challenge is present', () => { + let officialChallenge; let unofficialChallenges; + + before(async () => { + await user.update({ + 'permissions.challengeAdmin': true, + balance: 3, + }); + + officialChallenge = await generateChallenge(user, tavern, { + categories: [{ + name: 'habitica_official', + slug: 'habitica_official', + }], + prize: 1, + }); + await user.post(`/challenges/${officialChallenge._id}/join`); + + // We add 10 extra challenges to test whether the official challenge + // (the oldest) makes it to the front page. + unofficialChallenges = []; + for (let i = 0; i < 10; i += 1) { + const challenge = await generateChallenge(user, tavern, { prize: 1 }); // eslint-disable-line + await user.post(`/challenges/${challenge._id}/join`); // eslint-disable-line + unofficialChallenges.push(challenge); + } + }); + + it('should return official challenges first', async () => { + const challenges = await user.get('/challenges/groups/habitrpg'); + + const foundChallengeIndex = _.findIndex(challenges, { _id: officialChallenge._id }); + expect(foundChallengeIndex).to.eql(0); + }); + + it('should return newest challenges first, after official ones', async () => { + let challenges = await user.get('/challenges/groups/habitrpg'); + + unofficialChallenges.forEach((chal, index) => { + const foundChallengeIndex = _.findIndex(challenges, { _id: chal._id }); + expect(foundChallengeIndex).to.eql(10 - index); + }); + + const newChallenge = await generateChallenge(user, tavern, { prize: 1 }); + await user.post(`/challenges/${newChallenge._id}/join`); + + challenges = await user.get('/challenges/groups/habitrpg'); + + const foundChallengeIndex = _.findIndex(challenges, { _id: newChallenge._id }); + expect(foundChallengeIndex).to.eql(1); + }); + }); }); }); diff --git a/test/api/v3/integration/challenges/GET-challenges_user.test.js b/test/api/v3/integration/challenges/GET-challenges_user.test.js index cc9c3aabd7..190024e9de 100644 --- a/test/api/v3/integration/challenges/GET-challenges_user.test.js +++ b/test/api/v3/integration/challenges/GET-challenges_user.test.js @@ -2,39 +2,44 @@ import { generateUser, generateChallenge, createAndPopulateGroup, + resetHabiticaDB, } from '../../../../helpers/api-integration/v3'; +import { TAVERN_ID } from '../../../../../website/common/script/constants'; describe('GET challenges/user', () => { context('no official challenges', () => { - let user; let member; let nonMember; let challenge; let challenge2; - let publicGuild; let userData; let groupData; + let user; let member; let nonMember; let challenge; let challenge2; let publicChallenge; + let groupPlan; let userData; let groupData; let tavern; let tavernData; before(async () => { + await resetHabiticaDB(); + const { group, groupLeader, members } = await createAndPopulateGroup({ groupDetails: { name: 'TestGuild', type: 'guild', - privacy: 'public', + privacy: 'private', }, members: 1, + upgradeToGroupPlan: true, }); - publicGuild = group; + groupPlan = group; groupData = { - _id: publicGuild._id, + _id: groupPlan._id, categories: [], - id: publicGuild._id, - type: publicGuild.type, - privacy: publicGuild.privacy, - name: publicGuild.name, - summary: publicGuild.name, - leader: publicGuild.leader._id, + id: groupPlan._id, + type: groupPlan.type, + privacy: groupPlan.privacy, + name: groupPlan.name, + summary: groupPlan.name, + leader: groupPlan.leader._id, }; user = groupLeader; userData = { - _id: publicGuild.leader._id, - id: publicGuild.leader._id, + _id: groupPlan.leader._id, + id: groupPlan.leader._id, profile: { name: user.profile.name }, auth: { local: { @@ -46,17 +51,31 @@ describe('GET challenges/user', () => { }, }; + tavern = await user.get(`/groups/${TAVERN_ID}`); + tavernData = { + _id: TAVERN_ID, + categories: [], + id: TAVERN_ID, + type: tavern.type, + privacy: tavern.privacy, + name: tavern.name, + summary: tavern.name, + leader: tavern.leader._id, + }; + member = members[0]; // eslint-disable-line prefer-destructuring nonMember = await generateUser(); challenge = await generateChallenge(user, group); challenge2 = await generateChallenge(user, group); + await user.update({ balance: 0.25 }); + publicChallenge = await generateChallenge(user, tavern, { prize: 1 }); - await nonMember.post(`/challenges/${challenge._id}/join`); + await member.post(`/challenges/${challenge._id}/join`); }); context('all challenges', () => { it('should return challenges user has joined', async () => { - const challenges = await nonMember.get('/challenges/user?page=0'); + const challenges = await member.get('/challenges/user?page=0'); const foundChallenge = _.find(challenges, { _id: challenge._id }); expect(foundChallenge).to.exist; @@ -64,11 +83,13 @@ describe('GET challenges/user', () => { expect(foundChallenge.group).to.eql(groupData); }); - it('should not return challenges a non-member has not joined', async () => { + it('should return public challenges', async () => { const challenges = await nonMember.get('/challenges/user?page=0'); - const foundChallenge2 = _.find(challenges, { _id: challenge2._id }); - expect(foundChallenge2).to.not.exist; + const foundPublicChallenge = _.find(challenges, { _id: publicChallenge._id }); + expect(foundPublicChallenge).to.exist; + expect(foundPublicChallenge.leader).to.eql(userData); + expect(foundPublicChallenge.group).to.eql(tavernData); }); it('should return challenges user has created', async () => { @@ -100,10 +121,10 @@ describe('GET challenges/user', () => { it('should return newest challenges first', async () => { let challenges = await user.get('/challenges/user?page=0'); - let foundChallengeIndex = _.findIndex(challenges, { _id: challenge2._id }); + let foundChallengeIndex = _.findIndex(challenges, { _id: publicChallenge._id }); expect(foundChallengeIndex).to.eql(0); - const newChallenge = await generateChallenge(user, publicGuild); + const newChallenge = await generateChallenge(user, groupPlan); await user.post(`/challenges/${newChallenge._id}/join`); challenges = await user.get('/challenges/user?page=0'); @@ -113,52 +134,23 @@ describe('GET challenges/user', () => { }); it('should not return challenges user doesn\'t have access to', async () => { - const { group, groupLeader } = await createAndPopulateGroup({ - groupDetails: { - name: 'TestPrivateGuild', - summary: 'summary for TestPrivateGuild', - type: 'guild', - privacy: 'private', - }, - }); - - const privateChallenge = await generateChallenge(groupLeader, group); - await groupLeader.post(`/challenges/${privateChallenge._id}/join`); - const challenges = await nonMember.get('/challenges/user?page=0'); - const foundChallenge = _.find(challenges, { _id: privateChallenge._id }); + const foundChallenge = _.find(challenges, { _id: challenge._id }); expect(foundChallenge).to.not.exist; }); it('should not return challenges user doesn\'t have access to, even with query parameters', async () => { - const { group, groupLeader } = await createAndPopulateGroup({ - groupDetails: { - name: 'TestPrivateGuild', - summary: 'summary for TestPrivateGuild', - type: 'guild', - privacy: 'private', - }, - }); - - const privateChallenge = await generateChallenge(groupLeader, group, { - categories: [{ - name: 'academics', - slug: 'academics', - }], - }); - await groupLeader.post(`/challenges/${privateChallenge._id}/join`); - const challenges = await nonMember.get('/challenges/user?page=0&categories=academics&owned=not_owned'); - const foundChallenge = _.find(challenges, { _id: privateChallenge._id }); + const foundChallenge = _.find(challenges, { _id: challenge._id }); expect(foundChallenge).to.not.exist; }); }); context('my challenges', () => { it('should return challenges user has joined', async () => { - const challenges = await nonMember.get(`/challenges/user?page=0&member=${true}`); + const challenges = await member.get(`/challenges/user?page=0&member=${true}`); const foundChallenge = _.find(challenges, { _id: challenge._id }); expect(foundChallenge).to.exist; @@ -177,6 +169,10 @@ describe('GET challenges/user', () => { expect(foundChallenge2).to.exist; expect(foundChallenge2.leader).to.eql(userData); expect(foundChallenge2.group).to.eql(groupData); + const foundPublicChallenge = _.find(challenges, { _id: publicChallenge._id }); + expect(foundPublicChallenge).to.exist; + expect(foundPublicChallenge.leader).to.eql(userData); + expect(foundPublicChallenge.group).to.eql(tavernData); }); it('should return challenges user has created if filter by owned', async () => { @@ -190,6 +186,10 @@ describe('GET challenges/user', () => { expect(foundChallenge2).to.exist; expect(foundChallenge2.leader).to.eql(userData); expect(foundChallenge2.group).to.eql(groupData); + const foundPublicChallenge = _.find(challenges, { _id: publicChallenge._id }); + expect(foundPublicChallenge).to.exist; + expect(foundPublicChallenge.leader).to.eql(userData); + expect(foundPublicChallenge.group).to.eql(tavernData); }); it('should not return challenges user has created if filter by not owned', async () => { @@ -199,36 +199,40 @@ describe('GET challenges/user', () => { expect(foundChallenge1).to.not.exist; const foundChallenge2 = _.find(challenges, { _id: challenge2._id }); expect(foundChallenge2).to.not.exist; + const foundPublicChallenge = _.find(challenges, { _id: publicChallenge._id }); + expect(foundPublicChallenge).to.not.exist; }); it('should not return challenges in user groups', async () => { const challenges = await member.get(`/challenges/user?page=0&member=${true}`); - const foundChallenge1 = _.find(challenges, { _id: challenge._id }); - expect(foundChallenge1).to.not.exist; - const foundChallenge2 = _.find(challenges, { _id: challenge2._id }); expect(foundChallenge2).to.not.exist; }); + + it('should not return public challenges', async () => { + const challenges = await member.get(`/challenges/user?page=0&member=${true}`); + + const foundPublicChallenge = _.find(challenges, { _id: publicChallenge._id }); + expect(foundPublicChallenge).to.not.exist; + }); }); }); context('official challenge is present', () => { let user; let officialChallenge; let unofficialChallenges; let - publicGuild; + group; before(async () => { - const { group, groupLeader } = await createAndPopulateGroup({ + ({ group, groupLeader: user } = await createAndPopulateGroup({ groupDetails: { name: 'TestGuild', summary: 'summary for TestGuild', type: 'guild', - privacy: 'public', + privacy: 'private', }, - }); - - user = groupLeader; - publicGuild = group; + upgradeToGroupPlan: true, + })); await user.update({ 'permissions.challengeAdmin': true, @@ -271,7 +275,7 @@ describe('GET challenges/user', () => { } }); - const newChallenge = await generateChallenge(user, publicGuild); + const newChallenge = await generateChallenge(user, group); await user.post(`/challenges/${newChallenge._id}/join`); challenges = await user.get('/challenges/user?page=0'); @@ -294,9 +298,10 @@ describe('GET challenges/user', () => { groupDetails: { name: 'TestGuild', type: 'guild', - privacy: 'public', + privacy: 'private', }, members: 1, + upgradeToGroupPlan: true, }); user = groupLeader; diff --git a/test/api/v3/integration/challenges/POST-challenges.test.js b/test/api/v3/integration/challenges/POST-challenges.test.js index 3c566b76d9..fa490725cf 100644 --- a/test/api/v3/integration/challenges/POST-challenges.test.js +++ b/test/api/v3/integration/challenges/POST-challenges.test.js @@ -42,26 +42,7 @@ describe('POST /challenges', () => { }); }); - it('returns error when creating a challenge in a public guild and you are not a member of it', async () => { - const user = await generateUser(); - const { group } = await createAndPopulateGroup({ - groupDetails: { - type: 'guild', - privacy: 'public', - }, - }); - - await expect(user.post('/challenges', { - group: group._id, - prize: 4, - })).to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('mustBeGroupMember'), - }); - }); - - it('return error when creating a challenge with summary with greater than MAX_SUMMARY_SIZE_FOR_CHALLENGES characters', async () => { + it('returns error when creating a challenge with summary with greater than MAX_SUMMARY_SIZE_FOR_CHALLENGES characters', async () => { const user = await generateUser(); const summary = 'A'.repeat(MAX_SUMMARY_SIZE_FOR_CHALLENGES + 1); const group = createAndPopulateGroup({ @@ -77,7 +58,7 @@ describe('POST /challenges', () => { }); }); - context('Creating a challenge for a valid group', () => { + context('creating a Challenge for a Group Plan', () => { let groupLeader; let group; let groupMember; @@ -94,9 +75,11 @@ describe('POST /challenges', () => { challenges: true, }, }, + upgradeToGroupPlan: true, }); groupLeader = await populatedGroup.groupLeader.sync(); + await groupLeader.update({ permissions: {} }); group = populatedGroup.group; groupMember = populatedGroup.members[0]; // eslint-disable-line prefer-destructuring }); diff --git a/test/api/v3/integration/challenges/PUT-challenges_challengeId.test.js b/test/api/v3/integration/challenges/PUT-challenges_challengeId.test.js index 22e1f38b18..4e82da370a 100644 --- a/test/api/v3/integration/challenges/PUT-challenges_challengeId.test.js +++ b/test/api/v3/integration/challenges/PUT-challenges_challengeId.test.js @@ -18,6 +18,7 @@ describe('PUT /challenges/:challengeId', () => { privacy: 'private', }, members: 1, + upgradeToGroupPlan: true, }); privateGuild = group; diff --git a/test/api/v3/integration/chat/DELETE-chat_id.test.js b/test/api/v3/integration/chat/DELETE-chat_id.test.js index 7a9756e673..d0a323edfb 100644 --- a/test/api/v3/integration/chat/DELETE-chat_id.test.js +++ b/test/api/v3/integration/chat/DELETE-chat_id.test.js @@ -1,7 +1,6 @@ import { v4 as generateUUID } from 'uuid'; import { createAndPopulateGroup, - generateUser, translate as t, } from '../../../../helpers/api-integration/v3'; @@ -10,27 +9,30 @@ describe('DELETE /groups/:groupId/chat/:chatId', () => { admin; before(async () => { - const { group, groupLeader } = await createAndPopulateGroup({ + const { group, groupLeader, members } = await createAndPopulateGroup({ groupDetails: { type: 'guild', - privacy: 'public', + privacy: 'private', }, leaderDetails: { 'auth.timestamps.created': new Date('2022-01-01'), balance: 10, }, + members: 2, + upgradeToGroupPlan: true, }); groupWithChat = group; user = groupLeader; message = await user.post(`/groups/${groupWithChat._id}/chat`, { message: 'Some message' }); message = message.message; - userThatDidNotCreateChat = await generateUser(); - admin = await generateUser({ 'permissions.moderator': true }); + userThatDidNotCreateChat = members[0]; // eslint-disable-line prefer-destructuring + admin = members[1]; // eslint-disable-line prefer-destructuring + await admin.update({ permissions: { moderator: true } }); }); context('Chat errors', () => { - it('returns an error is message does not exist', async () => { + it('returns an error if message does not exist', async () => { const fakeChatId = generateUUID(); await expect(user.del(`/groups/${groupWithChat._id}/chat/${fakeChatId}`)).to.eventually.be.rejected.and.eql({ code: 404, @@ -56,7 +58,7 @@ describe('DELETE /groups/:groupId/chat/:chatId', () => { nextMessage = nextMessage.message; }); - it('allows creator to delete a their message', async () => { + it('allows creator to delete their message', async () => { await user.del(`/groups/${groupWithChat._id}/chat/${nextMessage.id}`); const returnedMessages = await user.get(`/groups/${groupWithChat._id}/chat/`); diff --git a/test/api/v3/integration/chat/GET-chat.test.js b/test/api/v3/integration/chat/GET-chat.test.js index 910b094c40..34f4316dad 100644 --- a/test/api/v3/integration/chat/GET-chat.test.js +++ b/test/api/v3/integration/chat/GET-chat.test.js @@ -1,6 +1,6 @@ import { generateUser, - generateGroup, + createAndPopulateGroup, translate as t, } from '../../../../helpers/api-integration/v3'; @@ -11,48 +11,22 @@ describe('GET /groups/:groupId/chat', () => { user = await generateUser(); }); - context('public Guild', () => { - let group; - - before(async () => { - const leader = await generateUser({ balance: 2 }); - - group = await generateGroup(leader, { - name: 'test group', - type: 'guild', - privacy: 'public', - }, { - chat: [ - { text: 'Hello', flags: {}, id: 1 }, - { text: 'Welcome to the Guild', flags: {}, id: 2 }, - ], - }); - }); - - it('returns Guild chat', async () => { - const chat = await user.get(`/groups/${group._id}/chat`); - - expect(chat[0].id).to.eql(group.chat[0].id); - expect(chat[1].id).to.eql(group.chat[1].id); - }); - }); - context('private Guild', () => { let group; - before(async () => { - const leader = await generateUser({ balance: 2 }); - - group = await generateGroup(leader, { - name: 'test group', - type: 'guild', - privacy: 'private', - }, { + ({ group } = await createAndPopulateGroup({ + groupDetails: { + name: 'test group', + type: 'guild', + privacy: 'private', + }, + members: 1, + upgradeToGroupPlan: true, chat: [ 'Hello', 'Welcome to the Guild', ], - }); + })); }); it('returns error if user is not member of requested private group', async () => { diff --git a/test/api/v3/integration/chat/POST-chat.flag.test.js b/test/api/v3/integration/chat/POST-chat.flag.test.js index 909fbfa7a0..21dfabec92 100644 --- a/test/api/v3/integration/chat/POST-chat.flag.test.js +++ b/test/api/v3/integration/chat/POST-chat.flag.test.js @@ -1,32 +1,42 @@ -import { find } from 'lodash'; +import find from 'lodash/find'; import moment from 'moment'; -import nconf from 'nconf'; import { IncomingWebhook } from '@slack/webhook'; import { - generateUser, + createAndPopulateGroup, translate as t, } from '../../../../helpers/api-integration/v3'; -const BASE_URL = nconf.get('BASE_URL'); - describe('POST /chat/:chatId/flag', () => { let user; let admin; let anotherUser; let newUser; let - group; + group; let members; let userToDelete; const TEST_MESSAGE = 'Test Message'; const USER_AGE_FOR_FLAGGING = 3; beforeEach(async () => { - user = await generateUser({ balance: 1, 'auth.timestamps.created': moment().subtract(USER_AGE_FOR_FLAGGING + 1, 'days').toDate() }); - admin = await generateUser({ balance: 1, 'permissions.moderator': true }); - anotherUser = await generateUser({ 'auth.timestamps.created': moment().subtract(USER_AGE_FOR_FLAGGING + 1, 'days').toDate() }); - newUser = await generateUser({ 'auth.timestamps.created': moment().subtract(1, 'days').toDate() }); - sandbox.stub(IncomingWebhook.prototype, 'send').returns(Promise.resolve()); + ({ group, groupLeader: user, members } = await createAndPopulateGroup({ + groupDetails: { + name: 'Test Guild', + type: 'guild', + privacy: 'private', + }, + leaderDetails: { + 'auth.timestamps.created': moment().subtract(USER_AGE_FOR_FLAGGING + 1, 'days').toDate(), + }, + members: 4, + upgradeToGroupPlan: true, + })); - group = await user.post('/groups', { - name: 'Test Guild', - type: 'guild', - privacy: 'public', + [admin, anotherUser, newUser, userToDelete] = members; + await user.update({ permissions: {} }); + await admin.update({ permissions: { moderator: true } }); + await anotherUser.update({ 'auth.timestamps.created': moment().subtract(USER_AGE_FOR_FLAGGING + 1, 'days').toDate() }); + await newUser.update({ 'auth.timestamps.created': moment().subtract(1, 'days').toDate() }); + await userToDelete.update({ + 'auth.timestamps.created': moment().subtract(1, 'days').toDate(), + 'purchased.plan.dateTerminated': moment().subtract(1, 'minutes').toDate(), }); + + sandbox.stub(IncomingWebhook.prototype, 'send').returns(Promise.resolve()); }); afterEach(() => { @@ -69,8 +79,8 @@ describe('POST /chat/:chatId/flag', () => { fallback: 'Flag Message', color: 'danger', author_name: `@${anotherUser.auth.local.username} ${anotherUser.profile.name} (${anotherUser.auth.local.email}; ${anotherUser._id})\n${timestamp}`, - title: 'Flag in Test Guild', - title_link: `${BASE_URL}/groups/guild/${group._id}`, + title: 'Flag in Test Guild - (private guild)', + title_link: undefined, text: TEST_MESSAGE, footer: ``, mrkdwn_in: [ @@ -78,7 +88,7 @@ describe('POST /chat/:chatId/flag', () => { ], }], }); - /* eslint-ensable camelcase */ + /* eslint-enable camelcase */ }); it('Does not increment message flag count and sends different message to moderator Slack when user is new', async () => { @@ -104,8 +114,8 @@ describe('POST /chat/:chatId/flag', () => { fallback: 'Flag Message', color: 'danger', author_name: `@${newUser.auth.local.username} ${newUser.profile.name} (${newUser.auth.local.email}; ${newUser._id})\n${timestamp}`, - title: 'Flag in Test Guild', - title_link: `${BASE_URL}/groups/guild/${group._id}`, + title: 'Flag in Test Guild - (private guild)', + title_link: undefined, text: TEST_MESSAGE, footer: ` ${automatedComment}`, mrkdwn_in: [ @@ -113,15 +123,12 @@ describe('POST /chat/:chatId/flag', () => { ], }], }); - /* eslint-ensable camelcase */ + /* eslint-enable camelcase */ }); it('Flags a chat when the author\'s account was deleted', async () => { - const deletedUser = await generateUser({ - 'auth.timestamps.created': new Date('2022-01-01'), - }); - const { message } = await deletedUser.post(`/groups/${group._id}/chat`, { message: TEST_MESSAGE }); - await deletedUser.del('/user', { + const { message } = await userToDelete.post(`/groups/${group._id}/chat`, { message: TEST_MESSAGE }); + await userToDelete.del('/user', { password: 'password', }); diff --git a/test/api/v3/integration/chat/POST-chat.like.test.js b/test/api/v3/integration/chat/POST-chat.like.test.js index beab851bb0..242d2d95f6 100644 --- a/test/api/v3/integration/chat/POST-chat.like.test.js +++ b/test/api/v3/integration/chat/POST-chat.like.test.js @@ -6,27 +6,27 @@ import { describe('POST /chat/:chatId/like', () => { let user; - let groupWithChat; - const testMessage = 'Test Message'; let anotherUser; + let groupWithChat; + let members; + const testMessage = 'Test Message'; before(async () => { - const { group, groupLeader, members } = await createAndPopulateGroup({ + ({ group: groupWithChat, groupLeader: user, members } = await createAndPopulateGroup({ groupDetails: { name: 'Test Guild', type: 'guild', - privacy: 'public', + privacy: 'private', }, members: 1, leaderDetails: { 'auth.timestamps.created': new Date('2022-01-01'), balance: 10, }, - }); + upgradeToGroupPlan: true, + })); - user = groupLeader; - groupWithChat = group; - anotherUser = members[0]; // eslint-disable-line prefer-destructuring + [anotherUser] = members; await anotherUser.update({ 'auth.timestamps.created': new Date('2022-01-01') }); }); diff --git a/test/api/v3/integration/chat/POST-chat.test.js b/test/api/v3/integration/chat/POST-chat.test.js index d88ddd1d2b..10e8e80bc3 100644 --- a/test/api/v3/integration/chat/POST-chat.test.js +++ b/test/api/v3/integration/chat/POST-chat.test.js @@ -1,41 +1,33 @@ import { IncomingWebhook } from '@slack/webhook'; -import nconf from 'nconf'; import { v4 as generateUUID } from 'uuid'; import { createAndPopulateGroup, - generateUser, translate as t, sleep, server, } from '../../../../helpers/api-integration/v3'; import { - SPAM_MESSAGE_LIMIT, SPAM_MIN_EXEMPT_CONTRIB_LEVEL, - TAVERN_ID, } from '../../../../../website/server/models/group'; -import { CHAT_FLAG_FROM_SHADOW_MUTE, MAX_MESSAGE_LENGTH } from '../../../../../website/common/script/constants'; +import { MAX_MESSAGE_LENGTH } from '../../../../../website/common/script/constants'; import * as email from '../../../../../website/server/libs/email'; -const BASE_URL = nconf.get('BASE_URL'); - describe('POST /chat', () => { let user; let groupWithChat; let member; let additionalMember; const testMessage = 'Test Message'; const testBannedWordMessage = 'TESTPLACEHOLDERSWEARWORDHERE'; - const testBannedWordMessage1 = 'TESTPLACEHOLDERSWEARWORDHERE1'; const testSlurMessage = 'message with TESTPLACEHOLDERSLURWORDHERE'; - const testSlurMessage1 = 'TESTPLACEHOLDERSLURWORDHERE1'; - const bannedWordErrorMessage = t('bannedWordUsed', { swearWordsUsed: testBannedWordMessage }); before(async () => { const { group, groupLeader, members } = await createAndPopulateGroup({ groupDetails: { name: 'Test Guild', type: 'guild', - privacy: 'public', + privacy: 'private', }, members: 2, + upgradeToGroupPlan: true, }); user = groupLeader; await user.update({ @@ -43,8 +35,7 @@ describe('POST /chat', () => { 'auth.timestamps.created': new Date('2022-01-01'), }); // prevent tests accidentally throwing messageGroupChatSpam groupWithChat = group; - member = members[0]; // eslint-disable-line prefer-destructuring - additionalMember = members[1]; // eslint-disable-line prefer-destructuring + [member, additionalMember] = members; await member.update({ 'auth.timestamps.created': new Date('2022-01-01') }); await additionalMember.update({ 'auth.timestamps.created': new Date('2022-01-01') }); }); @@ -89,32 +80,12 @@ describe('POST /chat', () => { member.update({ 'flags.chatRevoked': false }); }); - it('returns an error when chat privileges are revoked when sending a message to a public guild', async () => { - const userWithChatRevoked = await member.update({ 'flags.chatRevoked': true }); - await expect(userWithChatRevoked.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage })).to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('chatPrivilegesRevoked'), - }); - }); - it('does not error when chat privileges are revoked when sending a message to a private guild', async () => { - const { group, members } = await createAndPopulateGroup({ - groupDetails: { - name: 'Private Guild', - type: 'guild', - privacy: 'private', - }, - members: 1, - }); - - const privateGuildMemberWithChatsRevoked = members[0]; - await privateGuildMemberWithChatsRevoked.update({ + await member.update({ 'flags.chatRevoked': true, - 'auth.timestamps.created': new Date('2022-01-01'), }); - const message = await privateGuildMemberWithChatsRevoked.post(`/groups/${group._id}/chat`, { message: testMessage }); + const message = await member.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage }); expect(message.message.id).to.exist; }); @@ -152,54 +123,12 @@ describe('POST /chat', () => { member.update({ 'flags.chatShadowMuted': false }); }); - it('creates a chat with flagCount already set and notifies mods when sending a message to a public guild', async () => { - const userWithChatShadowMuted = await member.update({ 'flags.chatShadowMuted': true }); - const message = await userWithChatShadowMuted.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage }); - expect(message.message.id).to.exist; - expect(message.message.flagCount).to.eql(CHAT_FLAG_FROM_SHADOW_MUTE); - - // Email sent to mods - await sleep(0.5); - expect(email.sendTxn).to.be.calledOnce; - expect(email.sendTxn.args[0][1]).to.eql('shadow-muted-post-report-to-mods'); - - // Slack message to mods - expect(IncomingWebhook.prototype.send).to.be.calledOnce; - /* eslint-disable camelcase */ - expect(IncomingWebhook.prototype.send).to.be.calledWith({ - text: `@${member.auth.local.username} / ${member.profile.name} posted while shadow-muted`, - attachments: [{ - fallback: 'Shadow-Muted Message', - color: 'danger', - author_name: `@${member.auth.local.username} ${member.profile.name} (${member.auth.local.email}; ${member._id})`, - title: 'Shadow-Muted Post in Test Guild', - title_link: `${BASE_URL}/groups/guild/${groupWithChat.id}`, - text: testMessage, - mrkdwn_in: [ - 'text', - ], - }], - }); - /* eslint-enable camelcase */ - }); - it('creates a chat with zero flagCount when sending a message to a private guild', async () => { - const { group, members } = await createAndPopulateGroup({ - groupDetails: { - name: 'Private Guild', - type: 'guild', - privacy: 'private', - }, - members: 1, - }); - - const userWithChatShadowMuted = members[0]; - await userWithChatShadowMuted.update({ + await member.update({ 'flags.chatShadowMuted': true, - 'auth.timestamps.created': new Date('2022-01-01'), }); - const message = await userWithChatShadowMuted.post(`/groups/${group._id}/chat`, { message: testMessage }); + const message = await member.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage }); expect(message.message.id).to.exist; expect(message.message.flagCount).to.eql(0); @@ -226,100 +155,9 @@ describe('POST /chat', () => { expect(message.message.id).to.exist; expect(message.message.flagCount).to.eql(0); }); - - it('creates a chat with zero flagCount when non-shadow-muted user sends a message to a public guild', async () => { - const message = await member.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage }); - expect(message.message.id).to.exist; - expect(message.message.flagCount).to.eql(0); - }); }); context('banned word', () => { - it('returns an error when chat message contains a banned word in tavern', async () => { - await expect(user.post('/groups/habitrpg/chat', { message: testBannedWordMessage })) - .to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: bannedWordErrorMessage, - }); - }); - - it('returns an error when chat message contains a banned word in a public guild', async () => { - const { group, members } = await createAndPopulateGroup({ - groupDetails: { - name: 'public guild', - type: 'guild', - privacy: 'public', - }, - members: 1, - }); - - await expect(members[0].post(`/groups/${group._id}/chat`, { message: testBannedWordMessage })) - .to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: bannedWordErrorMessage, - }); - }); - - it('errors when word is part of a phrase', async () => { - const wordInPhrase = `phrase ${testBannedWordMessage} end`; - await expect(user.post('/groups/habitrpg/chat', { message: wordInPhrase })) - .to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: bannedWordErrorMessage, - }); - }); - - it('errors when word is surrounded by non alphabet characters', async () => { - const wordInPhrase = `_!${testBannedWordMessage}@_`; - await expect(user.post('/groups/habitrpg/chat', { message: wordInPhrase })) - .to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: bannedWordErrorMessage, - }); - }); - - it('errors when word is typed in mixed case', async () => { - const substrLength = Math.floor(testBannedWordMessage.length / 2); - const chatMessage = testBannedWordMessage.substring(0, substrLength).toLowerCase() - + testBannedWordMessage.substring(substrLength).toUpperCase(); - await expect(user.post('/groups/habitrpg/chat', { message: chatMessage })) - .to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: t('bannedWordUsed', { swearWordsUsed: chatMessage }), - }); - }); - - it('checks error message has all the banned words used, regardless of case', async () => { - const testBannedWords = [ - testBannedWordMessage.toUpperCase(), - testBannedWordMessage1.toLowerCase(), - ]; - const chatMessage = `Mixing ${testBannedWords[0]} and ${testBannedWords[1]} is bad for you.`; - await expect(user.post('/groups/habitrpg/chat', { message: chatMessage })) - .to.eventually.be.rejected - .and.have.property('message') - .that.includes(testBannedWords.join(', ')); - }); - - it('does not error when bad word is suffix of a word', async () => { - const wordAsSuffix = `prefix${testBannedWordMessage}`; - const message = await user.post('/groups/habitrpg/chat', { message: wordAsSuffix }); - - expect(message.message.id).to.exist; - }); - - it('does not error when bad word is prefix of a word', async () => { - const wordAsPrefix = `${testBannedWordMessage}suffix`; - const message = await user.post('/groups/habitrpg/chat', { message: wordAsPrefix }); - - expect(message.message.id).to.exist; - }); - it('does not error when sending a chat message containing a banned word to a party', async () => { const { group, members } = await createAndPopulateGroup({ groupDetails: { @@ -336,37 +174,8 @@ describe('POST /chat', () => { expect(message.message.id).to.exist; }); - it('does not error when sending a chat message containing a banned word to a public guild in which banned words are allowed', async () => { - const { group, members } = await createAndPopulateGroup({ - groupDetails: { - name: 'public guild', - type: 'guild', - privacy: 'public', - }, - members: 1, - }); - - // Update the bannedWordsAllowed property for the group - group.update({ bannedWordsAllowed: true }); - await members[0].update({ 'auth.timestamps.created': new Date('2022-01-01') }); - - const message = await members[0].post(`/groups/${group._id}/chat`, { message: testBannedWordMessage }); - - expect(message.message.id).to.exist; - }); - it('does not error when sending a chat message containing a banned word to a private guild', async () => { - const { group, members } = await createAndPopulateGroup({ - groupDetails: { - name: 'private guild', - type: 'guild', - privacy: 'private', - }, - members: 1, - }); - await members[0].update({ 'auth.timestamps.created': new Date('2022-01-01') }); - - const message = await members[0].post(`/groups/${group._id}/chat`, { message: testBannedWordMessage }); + const message = await member.post(`/groups/${groupWithChat._id}/chat`, { message: testBannedWordMessage }); expect(message.message.id).to.exist; }); @@ -383,45 +192,6 @@ describe('POST /chat', () => { user.update({ 'flags.chatRevoked': false }); }); - it('errors and revokes privileges when chat message contains a banned slur', async () => { - await expect(user.post(`/groups/${groupWithChat._id}/chat`, { message: testSlurMessage })).to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: t('bannedSlurUsed'), - }); - - // Email sent to mods - await sleep(0.5); - expect(email.sendTxn).to.be.calledOnce; - expect(email.sendTxn.args[0][1]).to.eql('slur-report-to-mods'); - - // Slack message to mods - expect(IncomingWebhook.prototype.send).to.be.calledOnce; - /* eslint-disable camelcase */ - expect(IncomingWebhook.prototype.send).to.be.calledWith({ - text: `${user.profile.name} (${user.id}) tried to post a slur`, - attachments: [{ - fallback: 'Slur Message', - color: 'danger', - author_name: `@${user.auth.local.username} ${user.profile.name} (${user.auth.local.email}; ${user._id})`, - title: 'Slur in Test Guild', - title_link: `${BASE_URL}/groups/guild/${groupWithChat.id}`, - text: testSlurMessage, - mrkdwn_in: [ - 'text', - ], - }], - }); - /* eslint-enable camelcase */ - - // Chat privileges are revoked - await expect(user.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage })).to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('chatPrivilegesRevoked'), - }); - }); - it('allows slurs in private groups', async () => { const { group, members } = await createAndPopulateGroup({ groupDetails: { @@ -437,28 +207,17 @@ describe('POST /chat', () => { expect(message.message.id).to.exist; }); - - it('errors when slur is typed in mixed case', async () => { - const substrLength = Math.floor(testSlurMessage1.length / 2); - const chatMessage = testSlurMessage1.substring(0, substrLength).toLowerCase() - + testSlurMessage1.substring(substrLength).toUpperCase(); - await expect(user.post('/groups/habitrpg/chat', { message: chatMessage })) - .to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: t('bannedSlurUsed'), - }); - }); }); it('errors when user account is too young', async () => { - const brandNewUser = await generateUser(); - await expect(brandNewUser.post('/groups/habitrpg/chat', { message: 'hi im new' })) + await user.update({ 'auth.timestamps.created': new Date() }); + await expect(user.post(`/groups/${groupWithChat._id}/chat`, { message: 'hi im new' })) .to.eventually.be.rejected.and.eql({ code: 400, error: 'BadRequest', message: t('chatTemporarilyUnavailable'), }); + await user.update({ 'auth.timestamps.created': new Date('2022-01-01') }); }); it('creates a chat', async () => { @@ -519,54 +278,42 @@ describe('POST /chat', () => { const mount = 'test-mount'; const pet = 'test-pet'; const style = 'test-style'; - const userWithStyle = await generateUser({ + await user.update({ 'items.currentMount': mount, 'items.currentPet': pet, 'preferences.style': style, - 'auth.timestamps.created': new Date('2022-01-01'), }); - await userWithStyle.sync(); - const message = await userWithStyle.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage }); + const message = await user.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage }); expect(message.message.id).to.exist; - expect(message.message.userStyles.items.currentMount).to.eql(userWithStyle.items.currentMount); - expect(message.message.userStyles.items.currentPet).to.eql(userWithStyle.items.currentPet); - expect(message.message.userStyles.preferences.style).to.eql(userWithStyle.preferences.style); - expect(message.message.userStyles.preferences.hair).to.eql(userWithStyle.preferences.hair); - expect(message.message.userStyles.preferences.skin).to.eql(userWithStyle.preferences.skin); - expect(message.message.userStyles.preferences.shirt).to.eql(userWithStyle.preferences.shirt); - expect(message.message.userStyles.preferences.chair).to.eql(userWithStyle.preferences.chair); + expect(message.message.userStyles.items.currentMount).to.eql(user.items.currentMount); + expect(message.message.userStyles.items.currentPet).to.eql(user.items.currentPet); + expect(message.message.userStyles.preferences.style).to.eql(user.preferences.style); + expect(message.message.userStyles.preferences.hair).to.eql(user.preferences.hair); + expect(message.message.userStyles.preferences.skin).to.eql(user.preferences.skin); + expect(message.message.userStyles.preferences.shirt).to.eql(user.preferences.shirt); + expect(message.message.userStyles.preferences.chair).to.eql(user.preferences.chair); expect(message.message.userStyles.preferences.background) - .to.eql(userWithStyle.preferences.background); + .to.eql(user.preferences.background); }); it('creates equipped to user styles', async () => { - const userWithStyle = await generateUser({ - 'preferences.costume': false, - 'auth.timestamps.created': new Date('2022-01-01'), - }); - await userWithStyle.sync(); - - const message = await userWithStyle.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage }); + const message = await user.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage }); expect(message.message.id).to.exist; expect(message.message.userStyles.items.gear.equipped) - .to.eql(userWithStyle.items.gear.equipped); + .to.eql(user.items.gear.equipped); expect(message.message.userStyles.items.gear.costume).to.not.exist; }); it('creates costume to user styles', async () => { - const userWithStyle = await generateUser({ - 'preferences.costume': true, - 'auth.timestamps.created': new Date('2022-01-01'), - }); - await userWithStyle.sync(); + await user.update({ 'preferences.costume': true }); - const message = await userWithStyle.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage }); + const message = await user.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage }); expect(message.message.id).to.exist; - expect(message.message.userStyles.items.gear.costume).to.eql(userWithStyle.items.gear.costume); + expect(message.message.userStyles.items.gear.costume).to.eql(user.items.gear.costume); expect(message.message.userStyles.items.gear.equipped).to.not.exist; }); @@ -576,12 +323,11 @@ describe('POST /chat', () => { tier: 800, tokensApplied: true, }; - const backer = await generateUser({ + await user.update({ backer: backerInfo, - 'auth.timestamps.created': new Date('2022-01-01'), }); - const message = await backer.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage }); + const message = await user.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage }); const messageBackerInfo = message.message.backer; expect(messageBackerInfo.npc).to.equal(backerInfo.npc); @@ -661,43 +407,5 @@ describe('POST /chat', () => { expect(memberWithNotification.newMessages[`${group._id}`]).to.exist; expect(memberWithNotification.notifications.find(n => n.type === 'NEW_CHAT_MESSAGE' && n.data.group.id === group._id)).to.exist; }); - - it('does not notify other users of a new message that is already hidden from shadow-muting', async () => { - await user.update({ 'flags.chatShadowMuted': true }); - const message = await user.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage }); - const memberWithNotification = await member.get('/user'); - - await user.update({ 'flags.chatShadowMuted': false }); - - expect(message.message.id).to.exist; - expect(memberWithNotification.newMessages[`${groupWithChat._id}`]).to.not.exist; - expect(memberWithNotification.notifications.find(n => n.type === 'NEW_CHAT_MESSAGE' && n.data.group.id === groupWithChat._id)).to.not.exist; - }); - }); - - context('Spam prevention', () => { - it('Returns an error when the user has been posting too many messages', async () => { - // Post as many messages are needed to reach the spam limit - for (let i = 0; i < SPAM_MESSAGE_LIMIT; i += 1) { - const result = await additionalMember.post(`/groups/${TAVERN_ID}/chat`, { message: testMessage }); // eslint-disable-line no-await-in-loop - expect(result.message.id).to.exist; - } - - await expect(additionalMember.post(`/groups/${TAVERN_ID}/chat`, { message: testMessage })).to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('messageGroupChatSpam'), - }); - }); - - it('contributor should not receive spam alert', async () => { - const userSocialite = await member.update({ 'contributor.level': SPAM_MIN_EXEMPT_CONTRIB_LEVEL }); - - // Post 1 more message than the spam limit to ensure they do not reach the limit - for (let i = 0; i < SPAM_MESSAGE_LIMIT + 1; i += 1) { - const result = await userSocialite.post(`/groups/${TAVERN_ID}/chat`, { message: testMessage }); // eslint-disable-line no-await-in-loop - expect(result.message.id).to.exist; - } - }); }); }); diff --git a/test/api/v3/integration/chat/POST-chat_seen.test.js b/test/api/v3/integration/chat/POST-chat_seen.test.js index 9cd2103bc6..89229f178f 100644 --- a/test/api/v3/integration/chat/POST-chat_seen.test.js +++ b/test/api/v3/integration/chat/POST-chat_seen.test.js @@ -12,18 +12,19 @@ describe('POST /groups/:id/chat/seen', () => { const { group, groupLeader, members } = await createAndPopulateGroup({ groupDetails: { type: 'guild', - privacy: 'public', + privacy: 'private', }, members: 1, leaderDetails: { 'auth.timestamps.created': new Date('2022-01-01'), balance: 10, }, + upgradeToGroupPlan: true, }); guild = group; guildLeader = groupLeader; - guildMember = members[0]; // eslint-disable-line prefer-destructuring + [guildMember] = members; guildMessage = await guildLeader.post(`/groups/${guild._id}/chat`, { message: 'Some guild message' }); guildMessage = guildMessage.message; diff --git a/test/api/v3/integration/chat/POST-groups_id_chat_id_clear_flags.test.js b/test/api/v3/integration/chat/POST-groups_id_chat_id_clear_flags.test.js index 9f98d73d85..13513cda1e 100644 --- a/test/api/v3/integration/chat/POST-groups_id_chat_id_clear_flags.test.js +++ b/test/api/v3/integration/chat/POST-groups_id_chat_id_clear_flags.test.js @@ -2,7 +2,6 @@ import moment from 'moment'; import { v4 as generateUUID } from 'uuid'; import { createAndPopulateGroup, - generateUser, translate as t, } from '../../../../helpers/api-integration/v3'; import config from '../../../../../config.json'; @@ -13,21 +12,24 @@ describe('POST /groups/:id/chat/:id/clearflags', () => { admin; before(async () => { - const { group, groupLeader } = await createAndPopulateGroup({ + const { group, groupLeader, members } = await createAndPopulateGroup({ groupDetails: { type: 'guild', - privacy: 'public', + privacy: 'private', }, leaderDetails: { 'auth.timestamps.created': new Date('2022-01-01'), balance: 10, }, + upgradeToGroupPlan: true, + members: 2, }); groupWithChat = group; author = groupLeader; - nonAdmin = await generateUser({ 'auth.timestamps.created': moment().subtract(USER_AGE_FOR_FLAGGING + 1, 'days').toDate() }); - admin = await generateUser({ 'permissions.moderator': true }); + [nonAdmin, admin] = members; + await nonAdmin.update({ 'auth.timestamps.created': moment().subtract(USER_AGE_FOR_FLAGGING + 1, 'days').toDate() }); + await admin.update({ 'permissions.moderator': true }); message = await author.post(`/groups/${groupWithChat._id}/chat`, { message: 'Some message' }); message = message.message; diff --git a/test/api/v3/integration/groups/GET-group-plans.test.js b/test/api/v3/integration/groups/GET-group-plans.test.js index 42e82d133d..8c050a98a2 100644 --- a/test/api/v3/integration/groups/GET-group-plans.test.js +++ b/test/api/v3/integration/groups/GET-group-plans.test.js @@ -1,6 +1,5 @@ import { - generateUser, - generateGroup, + createAndPopulateGroup, } from '../../../../helpers/api-integration/v3'; describe('GET /group-plans', () => { @@ -8,20 +7,15 @@ describe('GET /group-plans', () => { let groupPlan; before(async () => { - user = await generateUser({ balance: 4 }); - groupPlan = await generateGroup(user, - { - name: 'public guild - is member', + ({ group: groupPlan, groupLeader: user } = await createAndPopulateGroup({ + groupDetails: { + name: 'group plan - is member', type: 'guild', - privacy: 'public', + privacy: 'private', }, - { - purchased: { - plan: { - customerId: 'existings', - }, - }, - }); + upgradeToGroupPlan: true, + leaderDetails: { balance: 4 }, + })); }); it('returns group plans for the user', async () => { diff --git a/test/api/v3/integration/groups/GET-groups.test.js b/test/api/v3/integration/groups/GET-groups.test.js index 74c988e0b1..407cc21d29 100644 --- a/test/api/v3/integration/groups/GET-groups.test.js +++ b/test/api/v3/integration/groups/GET-groups.test.js @@ -1,70 +1,63 @@ import { - generateUser, + createAndPopulateGroup, resetHabiticaDB, generateGroup, translate as t, } from '../../../../helpers/api-integration/v3'; -import { - TAVERN_ID, -} from '../../../../../website/server/models/group'; -import apiError from '../../../../../website/server/libs/apiError'; describe('GET /groups', () => { - let user; - let userInGuild; - const NUMBER_OF_PUBLIC_GUILDS = 2; - const NUMBER_OF_PUBLIC_GUILDS_USER_IS_LEADER = 2; - const NUMBER_OF_PUBLIC_GUILDS_USER_IS_MEMBER = 1; - const NUMBER_OF_USERS_PRIVATE_GUILDS = 1; - const NUMBER_OF_GROUPS_USER_CAN_VIEW = 5; - const GUILD_PER_PAGE = 30; + let user; let leader; let members; + let secondGroup; let secondLeader; + const NUMBER_OF_USERS_PRIVATE_GUILDS = 2; + const NUMBER_OF_GROUPS_USER_CAN_VIEW = 3; const categories = [{ slug: 'newCat', name: 'New Category', }]; - let publicGuildNotMember; let privateGuildUserIsMemberOf; before(async () => { await resetHabiticaDB(); - const leader = await generateUser({ balance: 10 }); - user = await generateUser({ balance: 4 }); + ({ + group: privateGuildUserIsMemberOf, + groupLeader: leader, + members, + } = await createAndPopulateGroup({ + groupDetails: { + name: 'private guild - is member', + type: 'guild', + privacy: 'private', + categories, + }, + leaderDetails: { + balance: 10, + }, + members: 1, + upgradeToGroupPlan: true, + })); + [user] = members; + await user.update({ balance: 4 }); - const publicGuildUserIsMemberOf = await generateGroup(leader, { - name: 'public guild - is member', - type: 'guild', - privacy: 'public', - summary: 'ohayou kombonwa', - description: 'oyasumi', - }); - await leader.post(`/groups/${publicGuildUserIsMemberOf._id}/invite`, { uuids: [user._id] }); - await user.post(`/groups/${publicGuildUserIsMemberOf._id}/join`); + ({ group: secondGroup, groupLeader: secondLeader } = await createAndPopulateGroup({ + groupDetails: { + name: 'c++ coders', + type: 'guild', + privacy: 'private', + }, + upgradeToGroupPlan: true, + })); - userInGuild = await generateUser({ guilds: [publicGuildUserIsMemberOf._id] }); + await secondLeader.post(`/groups/${secondGroup._id}/invite`, { uuids: [user._id] }); + await user.post(`/groups/${secondGroup._id}/join`); - publicGuildNotMember = await generateGroup(leader, { - name: 'public guild - is not member', - type: 'guild', - privacy: 'public', - summary: 'Natsume Soseki', - description: 'Kinnosuke no Hondana', - categories, - }); - - privateGuildUserIsMemberOf = await generateGroup(leader, { - name: 'private guild - is member', - type: 'guild', - privacy: 'private', - categories, - }); - await leader.post(`/groups/${privateGuildUserIsMemberOf._id}/invite`, { uuids: [user._id] }); - await user.post(`/groups/${privateGuildUserIsMemberOf._id}/join`); - - await generateGroup(leader, { - name: 'private guild - is not member', - type: 'guild', - privacy: 'private', + await createAndPopulateGroup({ + groupDetails: { + name: 'private guild - is not member', + type: 'guild', + privacy: 'private', + }, + upgradeToGroupPlan: true, }); await generateGroup(leader, { @@ -98,172 +91,16 @@ describe('GET /groups', () => { }); }); - it('returns only the tavern when tavern passed in as query', async () => { - await expect(user.get('/groups?type=tavern')) - .to.eventually.have.a.lengthOf(1) - .and.to.have.nested.property('[0]') - .and.to.have.property('_id', TAVERN_ID); - }); - it('returns only the user\'s party when party passed in as query', async () => { await expect(user.get('/groups?type=party')) .to.eventually.have.a.lengthOf(1) .and.to.have.nested.property('[0]'); }); - it('returns all public guilds when publicGuilds passed in as query', async () => { - await expect(user.get('/groups?type=publicGuilds')) - .to.eventually.have.a.lengthOf(NUMBER_OF_PUBLIC_GUILDS); - }); - - describe('filters', () => { - it('returns public guilds filtered by category', async () => { - const guilds = await user.get(`/groups?type=publicGuilds&categories=${categories[0].slug}`); - - expect(guilds[0]._id).to.equal(publicGuildNotMember._id); - }); - - it('returns private guilds filtered by category', async () => { - const guilds = await user.get(`/groups?type=privateGuilds&categories=${categories[0].slug}`); - - expect(guilds[0]._id).to.equal(privateGuildUserIsMemberOf._id); - }); - - it('filters public guilds by size', async () => { - await generateGroup(user, { - name: 'guild1', - type: 'guild', - privacy: 'public', - memberCount: 1, - }); - - // @TODO: anyway to set higher memberCount in tests right now? - - const guilds = await user.get('/groups?type=publicGuilds&minMemberCount=3'); - - expect(guilds.length).to.equal(0); - }); - - it('filters private guilds by size', async () => { - await generateGroup(user, { - name: 'guild1', - type: 'guild', - privacy: 'private', - memberCount: 1, - }); - - // @TODO: anyway to set higher memberCount in tests right now? - - const guilds = await user.get('/groups?type=privateGuilds&minMemberCount=3'); - - expect(guilds.length).to.equal(0); - }); - - it('filters public guilds by leader role', async () => { - const guilds = await user.get('/groups?type=publicGuilds&leader=true'); - expect(guilds.length).to.equal(NUMBER_OF_PUBLIC_GUILDS_USER_IS_LEADER); - }); - - it('filters public guilds by member role', async () => { - const guilds = await userInGuild.get('/groups?type=publicGuilds&member=true'); - expect(guilds.length).to.equal(1); - expect(guilds[0].name).to.have.string('is member'); - }); - - it('filters public guilds by single-word search term', async () => { - const guilds = await user.get('/groups?type=publicGuilds&search=kom'); - expect(guilds.length).to.equal(1); - expect(guilds[0].summary).to.have.string('ohayou kombonwa'); - }); - - it('filters public guilds by single-word search term left and right-padded by spaces', async () => { - const guilds = await user.get('/groups?type=publicGuilds&search=++++ohayou+kombonwa+++++'); - expect(guilds.length).to.equal(1); - expect(guilds[0].summary).to.have.string('ohayou kombonwa'); - }); - - it('filters public guilds by two-words search term separated by multiple spaces', async () => { - const guilds = await user.get('/groups?type=publicGuilds&search=kinnosuke+++++hon'); - expect(guilds.length).to.equal(1); - expect(guilds[0].description).to.have.string('Kinnosuke'); - }); - }); - - describe('public guilds pagination', () => { - it('req.query.paginate must be a boolean string', async () => { - await expect(user.get('/groups?paginate=aString&type=publicGuilds')) - .to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: 'Invalid request parameters.', - }); - }); - - it('req.query.paginate can only be true when req.query.type includes publicGuilds', async () => { - await expect(user.get('/groups?paginate=true&type=notPublicGuilds')) - .to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: apiError('guildsOnlyPaginate'), - }); - }); - - it('req.query.page can\'t be negative', async () => { - await expect(user.get('/groups?paginate=true&page=-1&type=publicGuilds')) - .to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: 'Invalid request parameters.', - }); - }); - - it('returns 30 guilds per page ordered by number of members', async () => { - await user.update({ balance: 9000 }); - const delay = () => new Promise(resolve => setTimeout(resolve, 40)); - const promises = []; - - for (let i = 0; i < 60; i += 1) { - promises.push(generateGroup(user, { - name: `public guild ${i} - is member`, - type: 'guild', - privacy: 'public', - })); - await delay(); // eslint-disable-line no-await-in-loop - } - - const groups = await Promise.all(promises); - - // update group number 32 and not the first to make sure sorting works - await groups[32].update({ name: 'guild with most members', memberCount: 199 }); - await groups[33].update({ name: 'guild with less members', memberCount: -100 }); - - const page0 = await expect(user.get('/groups?type=publicGuilds&paginate=true')) - .to.eventually.have.a.lengthOf(GUILD_PER_PAGE); - expect(page0[0].name).to.equal('guild with most members'); - - await expect(user.get('/groups?type=publicGuilds&paginate=true&page=1')) - .to.eventually.have.a.lengthOf(GUILD_PER_PAGE); - const page2 = await expect(user.get('/groups?type=publicGuilds&paginate=true&page=2')) - // 1 created now, 4 by other tests, -1 for no more tavern. - .to.eventually.have.a.lengthOf(1 + 4 - 1); - expect(page2[3].name).to.equal('guild with less members'); - }).timeout(10000); - }); - - it('makes sure that the tavern doesn\'t show up when guilds is passed as a query', async () => { - const guilds = await user.get('/groups?type=guilds'); - expect(guilds.find(g => g.id === TAVERN_ID)).to.be.undefined; - }); - - it('makes sure that the tavern doesn\'t show up when publicGuilds is passed as a query', async () => { - const guilds = await user.get('/groups?type=publicGuilds'); - expect(guilds.find(g => g.id === TAVERN_ID)).to.be.undefined; - }); - it('returns all the user\'s guilds when guilds passed in as query', async () => { await expect(user.get('/groups?type=guilds')) .to.eventually.have.a - .lengthOf(NUMBER_OF_PUBLIC_GUILDS_USER_IS_MEMBER + NUMBER_OF_USERS_PRIVATE_GUILDS); + .lengthOf(NUMBER_OF_USERS_PRIVATE_GUILDS); }); it('returns all private guilds user is a part of when privateGuilds passed in as query', async () => { @@ -272,21 +109,21 @@ describe('GET /groups', () => { }); it('returns a list of groups user has access to', async () => { - await expect(user.get('/groups?type=privateGuilds,publicGuilds,party,tavern')) - .to.eventually.have.lengthOf(NUMBER_OF_GROUPS_USER_CAN_VIEW - 1); // -1 for no Tavern. + await expect(user.get('/groups?type=privateGuilds,party')) + .to.eventually.have.lengthOf(NUMBER_OF_GROUPS_USER_CAN_VIEW); }); - it('returns a list of groups user has access to', async () => { - const group = await generateGroup(user, { - name: 'c++ coders', - type: 'guild', - privacy: 'public', + describe('filters', () => { + it('returns private guilds filtered by category', async () => { + const guilds = await user.get(`/groups?type=privateGuilds&categories=${categories[0].slug}`); + + expect(guilds[0]._id).to.equal(privateGuildUserIsMemberOf._id); }); - // search for 'c++ coders' - await expect(user.get('/groups?type=publicGuilds&paginate=true&page=0&search=c%2B%2B+coders')) - .to.eventually.have.lengthOf(1) - .and.to.have.nested.property('[0]') - .and.to.have.property('_id', group._id); + it('filters private guilds by size', async () => { + const guilds = await user.get('/groups?type=privateGuilds&minMemberCount=3'); + + expect(guilds.length).to.equal(0); + }); }); }); diff --git a/test/api/v3/integration/groups/GET-groups_groupId_invites.test.js b/test/api/v3/integration/groups/GET-groups_groupId_invites.test.js index 8cf4e4f074..9ed08b392a 100644 --- a/test/api/v3/integration/groups/GET-groups_groupId_invites.test.js +++ b/test/api/v3/integration/groups/GET-groups_groupId_invites.test.js @@ -3,6 +3,7 @@ import { generateUser, generateGroup, translate as t, + createAndPopulateGroup, } from '../../../../helpers/api-integration/v3'; describe('GET /groups/:groupId/invites', () => { @@ -71,15 +72,16 @@ describe('GET /groups/:groupId/invites', () => { }); it('returns only first 30 invites by default (req.query.limit not specified)', async () => { - const leader = await generateUser({ balance: 4 }); - const group = await generateGroup(leader, { type: 'guild', privacy: 'public', name: generateUUID() }); - - const invitesToGenerate = []; - for (let i = 0; i < 31; i += 1) { - invitesToGenerate.push(generateUser()); - } - const generatedInvites = await Promise.all(invitesToGenerate); - await leader.post(`/groups/${group._id}/invite`, { uuids: generatedInvites.map(invite => invite._id) }); + const { group, groupLeader: leader } = await createAndPopulateGroup({ + groupDetails: { + type: 'guild', + privacy: 'private', + name: generateUUID(), + }, + leaderDetails: { balance: 4 }, + invites: 31, + upgradeToGroupPlan: true, + }); const res = await leader.get(`/groups/${group._id}/invites`); expect(res.length).to.equal(30); @@ -90,8 +92,16 @@ describe('GET /groups/:groupId/invites', () => { }).timeout(10000); it('returns an error if req.query.limit is over 60', async () => { - const leader = await generateUser({ balance: 4 }); - const group = await generateGroup(leader, { type: 'guild', privacy: 'public', name: generateUUID() }); + const { group, groupLeader: leader } = await createAndPopulateGroup({ + groupDetails: { + type: 'guild', + privacy: 'private', + name: generateUUID(), + }, + leaderDetails: { balance: 4 }, + invites: 1, + upgradeToGroupPlan: true, + }); await expect(leader.get(`/groups/${group._id}/invites?limit=61`)).to.eventually.be.rejected.and.eql({ code: 400, @@ -101,8 +111,16 @@ describe('GET /groups/:groupId/invites', () => { }); it('returns an error if req.query.limit is under 1', async () => { - const leader = await generateUser({ balance: 4 }); - const group = await generateGroup(leader, { type: 'guild', privacy: 'public', name: generateUUID() }); + const { group, groupLeader: leader } = await createAndPopulateGroup({ + groupDetails: { + type: 'guild', + privacy: 'private', + name: generateUUID(), + }, + leaderDetails: { balance: 4 }, + invites: 1, + upgradeToGroupPlan: true, + }); await expect(leader.get(`/groups/${group._id}/invites?limit=-1`)).to.eventually.be.rejected.and.eql({ code: 400, @@ -112,8 +130,16 @@ describe('GET /groups/:groupId/invites', () => { }); it('returns an error if req.query.limit is not an integer', async () => { - const leader = await generateUser({ balance: 4 }); - const group = await generateGroup(leader, { type: 'guild', privacy: 'public', name: generateUUID() }); + const { group, groupLeader: leader } = await createAndPopulateGroup({ + groupDetails: { + type: 'guild', + privacy: 'private', + name: generateUUID(), + }, + leaderDetails: { balance: 4 }, + invites: 1, + upgradeToGroupPlan: true, + }); await expect(leader.get(`/groups/${group._id}/invites?limit=1.3`)).to.eventually.be.rejected.and.eql({ code: 400, @@ -123,15 +149,16 @@ describe('GET /groups/:groupId/invites', () => { }); it('returns up to 60 invites when req.query.limit is specified', async () => { - const leader = await generateUser({ balance: 4 }); - const group = await generateGroup(leader, { type: 'guild', privacy: 'public', name: generateUUID() }); - - const invitesToGenerate = []; - for (let i = 0; i < 31; i += 1) { - invitesToGenerate.push(generateUser()); - } - const generatedInvites = await Promise.all(invitesToGenerate); - await leader.post(`/groups/${group._id}/invite`, { uuids: generatedInvites.map(invite => invite._id) }); + const { group, groupLeader: leader } = await createAndPopulateGroup({ + groupDetails: { + type: 'guild', + privacy: 'private', + name: generateUUID(), + }, + leaderDetails: { balance: 4 }, + invites: 31, + upgradeToGroupPlan: true, + }); let res = await leader.get(`/groups/${group._id}/invites?limit=14`); expect(res.length).to.equal(14); @@ -149,17 +176,20 @@ describe('GET /groups/:groupId/invites', () => { }).timeout(30000); it('supports using req.query.lastId to get more invites', async function test () { + let group; let invitees; this.timeout(30000); // @TODO: times out after 8 seconds - const leader = await generateUser({ balance: 4 }); - const group = await generateGroup(leader, { type: 'guild', privacy: 'public', name: generateUUID() }); + ({ group, groupLeader: user, invitees } = await createAndPopulateGroup({ + groupDetails: { + type: 'guild', + privacy: 'private', + name: generateUUID(), + }, + leaderDetails: { balance: 4 }, + invites: 32, + upgradeToGroupPlan: true, + })); - const invitesToGenerate = []; - for (let i = 0; i < 32; i += 1) { - invitesToGenerate.push(generateUser()); - } - const generatedInvites = await Promise.all(invitesToGenerate); // Group has 32 invites - const expectedIds = generatedInvites.map(generatedInvite => generatedInvite._id); - await user.post(`/groups/${group._id}/invite`, { uuids: expectedIds }); + const expectedIds = invitees.map(generatedInvite => generatedInvite._id); const res = await user.get(`/groups/${group._id}/invites`); expect(res.length).to.equal(30); diff --git a/test/api/v3/integration/groups/GET-groups_groupId_members.test.js b/test/api/v3/integration/groups/GET-groups_groupId_members.test.js index 5247df8197..42d02fc681 100644 --- a/test/api/v3/integration/groups/GET-groups_groupId_members.test.js +++ b/test/api/v3/integration/groups/GET-groups_groupId_members.test.js @@ -1,5 +1,6 @@ import { v4 as generateUUID } from 'uuid'; import { + createAndPopulateGroup, generateUser, generateGroup, translate as t, @@ -75,7 +76,15 @@ describe('GET /groups/:groupId/members', () => { }); it('req.query.includeAllPublicFields === true works with guilds', async () => { - const group = await generateGroup(user, { type: 'guild', name: generateUUID() }); + let group; + ({ group, groupLeader: user } = await createAndPopulateGroup({ + type: 'guild', + privacy: 'private', + name: generateUUID(), + upgradeToGroupPlan: true, + members: 1, + })); + const [memberRes] = await user.get(`/groups/${group._id}/members?includeAllPublicFields=true`); expect(memberRes).to.have.all.keys([ // works as: object has all and only these keys @@ -206,20 +215,20 @@ describe('GET /groups/:groupId/members', () => { it('supports using req.query.lastId to get more members', async function test () { this.timeout(30000); // @TODO: times out after 8 seconds - const leader = await generateUser({ balance: 4 }); - const group = await generateGroup(leader, { type: 'guild', privacy: 'public', name: generateUUID() }); + const { group, groupLeader: leader, members: generatedUsers } = await createAndPopulateGroup({ + type: 'guild', + privacy: 'private', + name: generateUUID(), + upgradeToGroupPlan: true, + leaderDetails: { balance: 4 }, + members: 57, + }); - const usersToGenerate = []; - for (let i = 0; i < 57; i += 1) { - usersToGenerate.push(generateUser({ guilds: [group._id] })); - } - // Group has 59 members (1 is the leader) - const generatedUsers = await Promise.all(usersToGenerate); const expectedIds = [leader._id].concat(generatedUsers.map(generatedUser => generatedUser._id)); - const res = await user.get(`/groups/${group._id}/members`); + const res = await leader.get(`/groups/${group._id}/members`); expect(res.length).to.equal(30); - const res2 = await user.get(`/groups/${group._id}/members?lastId=${res[res.length - 1]._id}`); + const res2 = await leader.get(`/groups/${group._id}/members?lastId=${res[res.length - 1]._id}`); expect(res2.length).to.equal(28); const resIds = res.concat(res2).map(member => member._id); diff --git a/test/api/v3/integration/groups/GET-groups_id.test.js b/test/api/v3/integration/groups/GET-groups_id.test.js index 7d1657db18..ea22cfe9a6 100644 --- a/test/api/v3/integration/groups/GET-groups_id.test.js +++ b/test/api/v3/integration/groups/GET-groups_id.test.js @@ -11,7 +11,6 @@ import { describe('GET /groups/:id', () => { const typesOfGroups = {}; - typesOfGroups['public guild'] = { type: 'guild', privacy: 'public' }; typesOfGroups['private guild'] = { type: 'guild', privacy: 'private' }; typesOfGroups.party = { type: 'party', privacy: 'private' }; @@ -24,10 +23,11 @@ describe('GET /groups/:id', () => { const groupData = await createAndPopulateGroup({ members: 30, groupDetails, + upgradeToGroupPlan: groupDetails.type === 'guild', }); leader = groupData.groupLeader; - member = groupData.members[0]; // eslint-disable-line prefer-destructuring + [member] = groupData.members; createdGroup = groupData.group; }); @@ -49,34 +49,6 @@ describe('GET /groups/:id', () => { }); }); - context('Non-member of a public guild', () => { - let nonMember; let - createdGroup; - - before(async () => { - const groupData = await createAndPopulateGroup({ - members: 1, - groupDetails: { - name: 'test guild', - type: 'guild', - privacy: 'public', - }, - }); - - createdGroup = groupData.group; - nonMember = await generateUser(); - }); - - it('returns the group object for a non-member', async () => { - const group = await nonMember.get(`/groups/${createdGroup._id}`); - - expect(group._id).to.eql(createdGroup._id); - expect(group.name).to.eql(createdGroup.name); - expect(group.type).to.eql(createdGroup.type); - expect(group.privacy).to.eql(createdGroup.privacy); - }); - }); - context('Non-member of a private guild', () => { let nonMember; let createdGroup; @@ -89,6 +61,7 @@ describe('GET /groups/:id', () => { type: 'guild', privacy: 'private', }, + upgradeToGroupPlan: true, }); createdGroup = groupData.group; @@ -218,7 +191,7 @@ describe('GET /groups/:id', () => { }); context('Flagged messages', () => { - let group; + let group; let members; const chat1 = { id: 'chat1', @@ -268,7 +241,7 @@ describe('GET /groups/:id', () => { groupDetails: { name: 'test guild', type: 'guild', - privacy: 'public', + privacy: 'private', chat: [ chat1, chat2, @@ -277,9 +250,11 @@ describe('GET /groups/:id', () => { chat5, ], }, + members: 1, + upgradeToGroupPlan: true, }); - group = groupData.group; + ({ group, members } = groupData); await group.addChat([chat1, chat2, chat3, chat4, chat5]); }); @@ -287,8 +262,8 @@ describe('GET /groups/:id', () => { context('non-admin', () => { let nonAdmin; - beforeEach(async () => { - nonAdmin = await generateUser(); + beforeEach(() => { + [nonAdmin] = members; }); it('does not include messages with a flag count of 2 or greater', async () => { @@ -314,9 +289,8 @@ describe('GET /groups/:id', () => { let admin; beforeEach(async () => { - admin = await generateUser({ - 'permissions.moderator': true, - }); + [admin] = members; + await admin.update({ permissions: { moderator: true } }); }); it('includes all messages', async () => { diff --git a/test/api/v3/integration/groups/POST-groups.test.js b/test/api/v3/integration/groups/POST-groups.test.js index e4b915f972..cb056c3e24 100644 --- a/test/api/v3/integration/groups/POST-groups.test.js +++ b/test/api/v3/integration/groups/POST-groups.test.js @@ -2,7 +2,6 @@ import { generateUser, translate as t, } from '../../../../helpers/api-integration/v3'; -import { model as Group } from '../../../../../website/server/models/group'; import { MAX_SUMMARY_SIZE_FOR_GUILDS } from '../../../../../website/common/script/constants'; describe('POST /group', () => { @@ -35,8 +34,8 @@ describe('POST /group', () => { it('sets the group leader to the user who created the group', async () => { const group = await user.post('/groups', { - name: 'Test Public Guild', - type: 'guild', + name: 'Test Party', + type: 'party', }); expect(group.leader).to.eql({ @@ -51,7 +50,7 @@ describe('POST /group', () => { const name = 'Test Group'; const group = await user.post('/groups', { name, - type: 'guild', + type: 'party', }); const updatedGroup = await user.get(`/groups/${group._id}`); @@ -64,7 +63,7 @@ describe('POST /group', () => { const summary = 'Test Summary'; const group = await user.post('/groups', { name, - type: 'guild', + type: 'party', summary, }); @@ -78,7 +77,7 @@ describe('POST /group', () => { const summary = 'A'.repeat(MAX_SUMMARY_SIZE_FOR_GUILDS + 1); await expect(user.post('/groups', { name, - type: 'guild', + type: 'party', summary, })).to.eventually.be.rejected.and.eql({ code: 400, @@ -88,157 +87,6 @@ describe('POST /group', () => { }); }); - context('Guilds', () => { - it('returns an error when a user with insufficient funds attempts to create a guild', async () => { - await user.update({ balance: 0 }); - - await expect( - user.post('/groups', { - name: 'Test Public Guild', - type: 'guild', - }), - ).to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('messageInsufficientGems'), - }); - }); - - it('adds guild to user\'s list of guilds', async () => { - const guild = await user.post('/groups', { - name: 'some guild', - type: 'guild', - privacy: 'public', - }); - - const updatedUser = await user.get('/user'); - - expect(updatedUser.guilds).to.include(guild._id); - }); - - it('awards the Joined Guild achievement', async () => { - await user.post('/groups', { - name: 'some guild', - type: 'guild', - privacy: 'public', - }); - - const updatedUser = await user.get('/user'); - - expect(updatedUser.achievements.joinedGuild).to.eql(true); - }); - - context('public guild', () => { - it('creates a group', async () => { - const groupName = 'Test Public Guild'; - const groupType = 'guild'; - const groupPrivacy = 'public'; - - const publicGuild = await user.post('/groups', { - name: groupName, - type: groupType, - privacy: groupPrivacy, - }); - - expect(publicGuild._id).to.exist; - expect(publicGuild.name).to.equal(groupName); - expect(publicGuild.type).to.equal(groupType); - expect(publicGuild.memberCount).to.equal(1); - expect(publicGuild.privacy).to.equal(groupPrivacy); - expect(publicGuild.leader).to.eql({ - _id: user._id, - profile: { - name: user.profile.name, - }, - }); - }); - - it('returns an error when a user with no chat privileges attempts to create a public guild', async () => { - await user.update({ 'flags.chatRevoked': true }); - - await expect( - user.post('/groups', { - name: 'Test Public Guild', - type: 'guild', - privacy: 'public', - }), - ).to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('chatPrivilegesRevoked'), - }); - }); - }); - - context('private guild', () => { - const groupName = 'Test Private Guild'; - const groupType = 'guild'; - const groupPrivacy = 'private'; - - it('creates a group', async () => { - const privateGuild = await user.post('/groups', { - name: groupName, - type: groupType, - privacy: groupPrivacy, - }); - - expect(privateGuild._id).to.exist; - expect(privateGuild.name).to.equal(groupName); - expect(privateGuild.type).to.equal(groupType); - expect(privateGuild.memberCount).to.equal(1); - expect(privateGuild.privacy).to.equal(groupPrivacy); - expect(privateGuild.leader).to.eql({ - _id: user._id, - profile: { - name: user.profile.name, - }, - }); - }); - - it('creates a private guild when the user has no chat privileges', async () => { - await user.update({ 'flags.chatRevoked': true }); - const privateGuild = await user.post('/groups', { - name: groupName, - type: groupType, - privacy: groupPrivacy, - }); - - expect(privateGuild._id).to.exist; - }); - - it('deducts gems from user and adds them to guild bank', async () => { - const privateGuild = await user.post('/groups', { - name: groupName, - type: groupType, - privacy: groupPrivacy, - }); - - expect(privateGuild.balance).to.eql(1); - - const updatedUser = await user.get('/user'); - - expect(updatedUser.balance).to.eql(user.balance - 1); - }); - - it('does not deduct the gems from user when guild creation fails', async () => { - const stub = sinon.stub(Group.prototype, 'save').rejects(); - const promise = user.post('/groups', { - name: groupName, - type: groupType, - privacy: groupPrivacy, - }); - - await expect(promise).to.eventually.be.rejected; - - const updatedUser = await user.get('/user'); - - expect(updatedUser.balance).to.eql(user.balance); - - stub.restore(); - }); - }); - }); - context('Parties', () => { const partyName = 'Test Party'; const partyType = 'party'; diff --git a/test/api/v3/integration/groups/POST-groups_groupId_join.test.js b/test/api/v3/integration/groups/POST-groups_groupId_join.test.js index 2c6313154b..a9bc3ed49f 100644 --- a/test/api/v3/integration/groups/POST-groups_groupId_join.test.js +++ b/test/api/v3/integration/groups/POST-groups_groupId_join.test.js @@ -18,81 +18,24 @@ describe('POST /group/:groupId/join', () => { }); }); - context('Joining a public guild', () => { - let user; let joiningUser; let - publicGuild; - - beforeEach(async () => { - const { group, groupLeader } = await createAndPopulateGroup({ - groupDetails: { - name: 'Test Guild', - type: 'guild', - privacy: 'public', - }, - }); - - publicGuild = group; - user = groupLeader; - joiningUser = await generateUser(); - }); - - it('allows non-invited users to join public guilds', async () => { - const res = await joiningUser.post(`/groups/${publicGuild._id}/join`); - - await expect(joiningUser.get('/user')).to.eventually.have.property('guilds').to.include(publicGuild._id); - expect(res.leader._id).to.eql(user._id); - expect(res.leader.profile.name).to.eql(user.profile.name); - }); - - it('returns an error if user was already a member', async () => { - await joiningUser.post(`/groups/${publicGuild._id}/join`); - await expect(joiningUser.post(`/groups/${publicGuild._id}/join`)).to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('youAreAlreadyInGroup'), - }); - }); - - it('promotes joining member in a public empty guild to leader', async () => { - await user.post(`/groups/${publicGuild._id}/leave`); - - await joiningUser.post(`/groups/${publicGuild._id}/join`); - - await expect(joiningUser.get(`/groups/${publicGuild._id}`)).to.eventually.have.nested.property('leader._id', joiningUser._id); - }); - - it('increments memberCount when joining guilds', async () => { - const oldMemberCount = publicGuild.memberCount; - - await joiningUser.post(`/groups/${publicGuild._id}/join`); - - await expect(joiningUser.get(`/groups/${publicGuild._id}`)).to.eventually.have.property('memberCount', oldMemberCount + 1); - }); - - it('awards Joined Guild achievement', async () => { - await joiningUser.post(`/groups/${publicGuild._id}/join`); - - await expect(joiningUser.get('/user')).to.eventually.have.nested.property('achievements.joinedGuild', true); - }); - }); - context('Joining a private guild', () => { - let user; let invitedUser; let - guild; + let user; + let invitedUser; + let guild; + let invitees; beforeEach(async () => { - const { group, groupLeader, invitees } = await createAndPopulateGroup({ + ({ group: guild, groupLeader: user, invitees } = await createAndPopulateGroup({ groupDetails: { name: 'Test Guild', type: 'guild', privacy: 'private', }, invites: 1, - }); + upgradeToGroupPlan: true, + })); - guild = group; - user = groupLeader; - invitedUser = invitees[0]; // eslint-disable-line prefer-destructuring + [invitedUser] = invitees; }); it('returns error when user is not invited to private guild', async () => { @@ -182,7 +125,7 @@ describe('POST /group/:groupId/join', () => { party = group; user = groupLeader; - invitedUser = invitees[0]; // eslint-disable-line prefer-destructuring + [invitedUser] = invitees; }); it('returns error when user is not invited to party', async () => { diff --git a/test/api/v3/integration/groups/POST-groups_groupId_leave.test.js b/test/api/v3/integration/groups/POST-groups_groupId_leave.test.js index b7b16a7f04..523c7b9d65 100644 --- a/test/api/v3/integration/groups/POST-groups_groupId_leave.test.js +++ b/test/api/v3/integration/groups/POST-groups_groupId_leave.test.js @@ -5,7 +5,6 @@ import { generateChallenge, checkExistence, createAndPopulateGroup, - sleep, generateUser, translate as t, } from '../../../../helpers/api-integration/v3'; @@ -14,253 +13,187 @@ import payments from '../../../../../website/server/libs/payments/payments'; import calculateSubscriptionTerminationDate from '../../../../../website/server/libs/payments/calculateSubscriptionTerminationDate'; describe('POST /groups/:groupId/leave', () => { - const typesOfGroups = { - 'public guild': { type: 'guild', privacy: 'public' }, - 'private guild': { type: 'guild', privacy: 'private' }, - party: { type: 'party', privacy: 'private' }, - }; + let groupToLeave; + let leader; + let member; + let members; + let memberCount; - each(typesOfGroups, (groupDetails, groupType) => { - context(`Leaving a ${groupType}`, () => { - let groupToLeave; - let leader; - let member; - let memberCount; + context('Leaving a Group Plan', () => { + beforeEach(async () => { + ({ group: groupToLeave, groupLeader: leader, members } = await createAndPopulateGroup({ + type: 'guild', + privacy: 'private', + members: 1, + upgradeToGroupPlan: true, + })); + + [member] = members; + memberCount = groupToLeave.memberCount; + await leader.update({ 'auth.timestamps.created': new Date('2022-01-01') }); + }); + + it('prevents non members from leaving', async () => { + const user = await generateUser(); + await expect(user.post(`/groups/${groupToLeave._id}/leave`)).to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('groupNotFound'), + }); + }); + + it('lets user leave', async () => { + await member.post(`/groups/${groupToLeave._id}/leave`); + + const userThatLeftGroup = await member.get('/user'); + + expect(userThatLeftGroup.guilds).to.be.empty; + expect(userThatLeftGroup.party._id).to.not.exist; + await groupToLeave.sync(); + expect(groupToLeave.memberCount).to.equal(memberCount - 1); + }); + + it('removes new messages for that group from user', async () => { + await leader.post(`/groups/${groupToLeave._id}/chat`, { message: 'Some message' }); + await member.sync(); + + expect(member.notifications.find(n => n.type === 'NEW_CHAT_MESSAGE' && n.data.group.id === groupToLeave._id)).to.exist; + expect(member.newMessages[groupToLeave._id]).to.not.be.empty; + + await member.post(`/groups/${groupToLeave._id}/leave`); + await member.sync(); + + expect(member.notifications.find(n => n.type === 'NEW_CHAT_MESSAGE' && n.data.group.id === groupToLeave._id)).to.not.exist; + expect(member.newMessages[groupToLeave._id]).to.be.undefined; + }); + + context('with challenges', () => { + let challenge; beforeEach(async () => { - const { group, groupLeader, members } = await createAndPopulateGroup({ - groupDetails, - members: 1, - }); + challenge = await generateChallenge(leader, groupToLeave); + await member.post(`/challenges/${challenge._id}/join`); - groupToLeave = group; - leader = groupLeader; - member = members[0]; // eslint-disable-line prefer-destructuring - memberCount = group.memberCount; - await members[0].update({ 'auth.timestamps.created': new Date('2022-01-01') }); - }); - - it('prevents non members from leaving', async () => { - const user = await generateUser(); - await expect(user.post(`/groups/${groupToLeave._id}/leave`)).to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('groupNotFound'), + await leader.post(`/tasks/challenge/${challenge._id}`, { + text: 'test habit', + type: 'habit', }); }); - it(`lets user leave a ${groupType}`, async () => { + it('removes all challenge tasks when keep parameter is set to remove', async () => { + await member.post(`/groups/${groupToLeave._id}/leave?keep=remove-all`); + + const userWithoutChallengeTasks = await member.get('/user'); + + expect(userWithoutChallengeTasks.challenges).to.not.include(challenge._id); + expect(userWithoutChallengeTasks.tasksOrder.habits).to.be.empty; + }); + + it('keeps all challenge tasks when keep parameter is not set', async () => { await member.post(`/groups/${groupToLeave._id}/leave`); - const userThatLeftGroup = await member.get('/user'); + const userWithChallengeTasks = await member.get('/user'); - expect(userThatLeftGroup.guilds).to.be.empty; - expect(userThatLeftGroup.party._id).to.not.exist; - await groupToLeave.sync(); - expect(groupToLeave.memberCount).to.equal(memberCount - 1); + expect(userWithChallengeTasks.tasksOrder.habits).to.not.be.empty; }); - it(`sets a new group leader when leader leaves a ${groupType}`, async () => { - await leader.post(`/groups/${groupToLeave._id}/leave`); + it('keeps the user in the challenge when the keepChallenges parameter is set to remain-in-challenges', async () => { + await member.post(`/groups/${groupToLeave._id}/leave`, { keepChallenges: 'remain-in-challenges' }); - await groupToLeave.sync(); - expect(groupToLeave.memberCount).to.equal(memberCount - 1); - expect(groupToLeave.leader).to.equal(member._id); + const userWithChallengeTasks = await member.get('/user'); + + expect(userWithChallengeTasks.challenges).to.include(challenge._id); }); - it('removes new messages for that group from user', async () => { - await member.post(`/groups/${groupToLeave._id}/chat`, { message: 'Some message' }); + it('drops the user in the challenge when the keepChallenges parameter isn\'t set', async () => { + await member.post(`/groups/${groupToLeave._id}/leave`); - await sleep(0.5); + const userWithChallengeTasks = await member.get('/user'); - await leader.sync(); - - expect(leader.notifications.find(n => 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 => n.type === 'NEW_CHAT_MESSAGE' && n.data.group.id === groupToLeave._id)).to.not.exist; - expect(leader.newMessages[groupToLeave._id]).to.be.undefined; + expect(userWithChallengeTasks.challenges).to.not.include(challenge._id); }); - - context('with challenges', () => { - let challenge; - - beforeEach(async () => { - challenge = await generateChallenge(leader, groupToLeave); - await leader.post(`/challenges/${challenge._id}/join`); - - await leader.post(`/tasks/challenge/${challenge._id}`, { - text: 'test habit', - type: 'habit', - }); - - await sleep(0.5); - }); - - it('removes all challenge tasks when keep parameter is set to remove', async () => { - await leader.post(`/groups/${groupToLeave._id}/leave?keep=remove-all`); - - const userWithoutChallengeTasks = await leader.get('/user'); - - expect(userWithoutChallengeTasks.challenges).to.not.include(challenge._id); - expect(userWithoutChallengeTasks.tasksOrder.habits).to.be.empty; - }); - - it('keeps all challenge tasks when keep parameter is not set', async () => { - await leader.post(`/groups/${groupToLeave._id}/leave`); - - const userWithChallengeTasks = await leader.get('/user'); - - // @TODO find elegant way to assert against the task existing - expect(userWithChallengeTasks.tasksOrder.habits).to.not.be.empty; - }); - - it('keeps the user in the challenge when the keepChallenges parameter is set to remain-in-challenges', async () => { - await leader.post(`/groups/${groupToLeave._id}/leave`, { keepChallenges: 'remain-in-challenges' }); - - const userWithChallengeTasks = await leader.get('/user'); - - expect(userWithChallengeTasks.challenges).to.include(challenge._id); - }); - - it('drops the user in the challenge when the keepChallenges parameter isn\'t set', async () => { - await leader.post(`/groups/${groupToLeave._id}/leave`); - - const userWithChallengeTasks = await leader.get('/user'); - - expect(userWithChallengeTasks.challenges).to.not.include(challenge._id); - }); - }); - - it('prevents quest leader from leaving a groupToLeave'); - it('prevents a user from leaving during an active quest'); }); }); - context('Leaving a group as the last member', () => { - context('private guild', () => { - let privateGuild; - let leader; - let invitedUser; + context('Leaving a Party', () => { + let invitees; + let invitedUser; - beforeEach(async () => { - const { group, groupLeader, invitees } = await createAndPopulateGroup({ - groupDetails: { - name: 'Test Private Guild', - type: 'guild', - }, - invites: 1, - leaderDetails: { - 'auth.timestamps.created': new Date('2022-01-01'), - balance: 10, - }, - }); + beforeEach(async () => { + ({ + group: groupToLeave, + groupLeader: leader, + members, + invitees, + } = await createAndPopulateGroup({ + type: 'party', + privacy: 'private', + members: 1, + invites: 1, + })); - privateGuild = group; - leader = groupLeader; - invitedUser = invitees[0]; // eslint-disable-line prefer-destructuring + [member] = members; + [invitedUser] = invitees; + memberCount = groupToLeave.memberCount; + await leader.update({ 'auth.timestamps.created': new Date('2022-01-01') }); + }); - await leader.post(`/groups/${group._id}/chat`, { message: 'Some message' }); - }); - - it('removes a group when the last member leaves', async () => { - await leader.post(`/groups/${privateGuild._id}/leave`); - - await expect(checkExistence('groups', privateGuild._id)).to.eventually.equal(false); - }); - - it('removes invitations when the last member leaves', async () => { - await leader.post(`/groups/${privateGuild._id}/leave`); - - const userWithoutInvitation = await invitedUser.get('/user'); - - expect(userWithoutInvitation.invitations.guilds).to.be.empty; + it('prevents non members from leaving', async () => { + const user = await generateUser(); + await expect(user.post(`/groups/${groupToLeave._id}/leave`)).to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('groupNotFound'), }); }); - context('public guild', () => { - let publicGuild; - let leader; - let invitedUser; + it('lets user leave', async () => { + await member.post(`/groups/${groupToLeave._id}/leave`); - beforeEach(async () => { - const { group, groupLeader, invitees } = await createAndPopulateGroup({ - groupDetails: { - name: 'Test Public Guild', - type: 'guild', - privacy: 'public', - }, - invites: 1, - }); + const userThatLeftGroup = await member.get('/user'); - publicGuild = group; - leader = groupLeader; - invitedUser = invitees[0]; // eslint-disable-line prefer-destructuring - }); - - it('keeps the group when the last member leaves', async () => { - await leader.post(`/groups/${publicGuild._id}/leave`); - - await expect(checkExistence('groups', publicGuild._id)).to.eventually.equal(true); - }); - - it('keeps the invitations when the last member leaves a public guild', async () => { - await leader.post(`/groups/${publicGuild._id}/leave`); - - const userWithoutInvitation = await invitedUser.get('/user'); - - expect(userWithoutInvitation.invitations.guilds).to.not.be.empty; - }); - - it('deletes non existent guild from user when user tries to leave', async () => { - const nonExistentGuildId = generateUUID(); - const userWithNonExistentGuild = await generateUser({ guilds: [nonExistentGuildId] }); - expect(userWithNonExistentGuild.guilds).to.contain(nonExistentGuildId); - - await expect(userWithNonExistentGuild.post(`/groups/${nonExistentGuildId}/leave`)) - .to.eventually.be.rejected; - - await userWithNonExistentGuild.sync(); - - expect(userWithNonExistentGuild.guilds).to.not.contain(nonExistentGuildId); - }); + expect(userThatLeftGroup.guilds).to.be.empty; + expect(userThatLeftGroup.party._id).to.not.exist; + await groupToLeave.sync(); + expect(groupToLeave.memberCount).to.equal(memberCount - 1); }); - context('party', () => { - let party; - let leader; - let invitedUser; + it('sets a new group leader when leader leaves', async () => { + await leader.post(`/groups/${groupToLeave._id}/leave`); - beforeEach(async () => { - const { group, groupLeader, invitees } = await createAndPopulateGroup({ - groupDetails: { - name: 'Test Party', - type: 'party', - }, - invites: 1, - }); + await groupToLeave.sync(); + expect(groupToLeave.memberCount).to.equal(memberCount - 1); + expect(groupToLeave.leader).to.equal(member._id); + }); - party = group; - leader = groupLeader; - invitedUser = invitees[0]; // eslint-disable-line prefer-destructuring - }); + it('removes new messages for that group from user', async () => { + await leader.post(`/groups/${groupToLeave._id}/chat`, { message: 'Some message' }); + await member.sync(); - it('removes a group when the last member leaves a party', async () => { - await leader.post(`/groups/${party._id}/leave`); + expect(member.notifications.find(n => n.type === 'NEW_CHAT_MESSAGE' && n.data.group.id === groupToLeave._id)).to.exist; + expect(member.newMessages[groupToLeave._id]).to.not.be.empty; - await expect(checkExistence('party', party._id)).to.eventually.equal(false); - }); + await member.post(`/groups/${groupToLeave._id}/leave`); + await member.sync(); - it('removes invitations when the last member leaves a party', async () => { - await leader.post(`/groups/${party._id}/leave`); + expect(member.notifications.find(n => n.type === 'NEW_CHAT_MESSAGE' && n.data.group.id === groupToLeave._id)).to.not.exist; + expect(member.newMessages[groupToLeave._id]).to.be.undefined; + }); - const userWithoutInvitation = await invitedUser.get('/user'); + it('removes a party when the last member leaves', async () => { + await member.post(`/groups/${groupToLeave._id}/leave`); + await leader.post(`/groups/${groupToLeave._id}/leave`); - expect(userWithoutInvitation.invitations.parties[0]).to.be.undefined; - }); + await expect(checkExistence('party', groupToLeave._id)).to.eventually.equal(false); + }); + + it('removes invitations when the last member leaves a party', async () => { + await member.post(`/groups/${groupToLeave._id}/leave`); + await leader.post(`/groups/${groupToLeave._id}/leave`); + + const userWithoutInvitation = await invitedUser.get('/user'); + + expect(userWithoutInvitation.invitations.parties[0]).to.be.undefined; }); it('deletes non existent party from user when user tries to leave', async () => { @@ -275,23 +208,71 @@ describe('POST /groups/:groupId/leave', () => { expect(userWithNonExistentParty.party).to.eql({}); }); + + context('with challenges', () => { + let challenge; + + beforeEach(async () => { + challenge = await generateChallenge(leader, groupToLeave); + await member.post(`/challenges/${challenge._id}/join`); + + await leader.post(`/tasks/challenge/${challenge._id}`, { + text: 'test habit', + type: 'habit', + }); + }); + + it('removes all challenge tasks when keep parameter is set to remove', async () => { + await member.post(`/groups/${groupToLeave._id}/leave?keep=remove-all`); + + const userWithoutChallengeTasks = await member.get('/user'); + + expect(userWithoutChallengeTasks.challenges).to.not.include(challenge._id); + expect(userWithoutChallengeTasks.tasksOrder.habits).to.be.empty; + }); + + it('keeps all challenge tasks when keep parameter is not set', async () => { + await member.post(`/groups/${groupToLeave._id}/leave`); + + const userWithChallengeTasks = await member.get('/user'); + + expect(userWithChallengeTasks.tasksOrder.habits).to.not.be.empty; + }); + + it('keeps the user in the challenge when the keepChallenges parameter is set to remain-in-challenges', async () => { + await member.post(`/groups/${groupToLeave._id}/leave`, { keepChallenges: 'remain-in-challenges' }); + + const userWithChallengeTasks = await member.get('/user'); + + expect(userWithChallengeTasks.challenges).to.include(challenge._id); + }); + + it('drops the user in the challenge when the keepChallenges parameter isn\'t set', async () => { + await member.post(`/groups/${groupToLeave._id}/leave`); + + const userWithChallengeTasks = await member.get('/user'); + + expect(userWithChallengeTasks.challenges).to.not.include(challenge._id); + }); + }); }); + const typesOfGroups = { + 'private guild': { type: 'guild', privacy: 'private' }, + party: { type: 'party', privacy: 'private' }, + }; + each(typesOfGroups, (groupDetails, groupType) => { context(`Leaving a group plan when the group is a ${groupType}`, () => { - if (groupDetails.privacy === 'public') return; // public guilds cannot be group plans let groupWithPlan; - let leader; - let member; beforeEach(async () => { - const { group, groupLeader, members } = await createAndPopulateGroup({ + ({ group: groupWithPlan, groupLeader: leader, members } = await createAndPopulateGroup({ groupDetails, members: 1, - }); - leader = groupLeader; - member = members[0]; // eslint-disable-line prefer-destructuring - groupWithPlan = group; + upgradeToGroupPlan: true, + })); + [member] = members; const userWithFreePlan = await User.findById(leader._id).exec(); // Create subscription @@ -321,45 +302,21 @@ describe('POST /groups/:groupId/leave', () => { await member.sync(); expect(member.purchased.plan.dateTerminated).to.exist; }); - - it('preserves the free subscription when leaving a any other group without a plan', async () => { - // Joining a guild without a group plan - const { group: groupWithNoPlan } = await createAndPopulateGroup({ - groupDetails: { - name: 'Group Without Plan', - type: 'guild', - privacy: 'public', - }, - }); - - await member.post(`/groups/${groupWithNoPlan._id}/join`); - await member.sync(); - expect(member.purchased.plan.planId).to.equal('group_plan_auto'); - expect(member.purchased.plan.dateTerminated).to.not.exist; - - // Leaving the guild without a group plan - await member.post(`/groups/${groupWithNoPlan._id}/leave`); - await member.sync(); - expect(member.purchased.plan.dateTerminated).to.not.exist; - }); }); }); each(typesOfGroups, (groupDetails, groupType) => { context(`Leaving a group with extraMonths left plan when the group is a ${groupType}`, () => { - if (groupDetails.privacy === 'public') return; // public guilds cannot be group plans const extraMonths = 12; let groupWithPlan; - let member; beforeEach(async () => { - const { group, members } = await createAndPopulateGroup({ + ({ group: groupWithPlan, members } = await createAndPopulateGroup({ groupDetails, members: 1, upgradeToGroupPlan: true, - }); + })); [member] = members; - groupWithPlan = group; await member.update({ 'purchased.plan.extraMonths': extraMonths, }); diff --git a/test/api/v3/integration/groups/POST-groups_groupId_reject.test.js b/test/api/v3/integration/groups/POST-groups_groupId_reject.test.js index 68fba0e772..ccc8e761c9 100644 --- a/test/api/v3/integration/groups/POST-groups_groupId_reject.test.js +++ b/test/api/v3/integration/groups/POST-groups_groupId_reject.test.js @@ -5,43 +5,6 @@ import { } from '../../../../helpers/api-integration/v3'; describe('POST /group/:groupId/reject-invite', () => { - context('Rejecting a public guild invite', () => { - let publicGuild; let - invitedUser; - - beforeEach(async () => { - const { group, invitees } = await createAndPopulateGroup({ - groupDetails: { - name: 'Test Guild', - type: 'guild', - privacy: 'public', - }, - invites: 1, - }); - - publicGuild = group; - invitedUser = invitees[0]; // eslint-disable-line prefer-destructuring - }); - - it('returns error when user is not invited', async () => { - const userWithoutInvite = await generateUser(); - - await expect(userWithoutInvite.post(`/groups/${publicGuild._id}/reject-invite`)).to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('messageGroupRequiresInvite'), - }); - }); - - it('clears invitation from user', async () => { - await invitedUser.post(`/groups/${publicGuild._id}/reject-invite`); - - await expect(invitedUser.get('/user')) - .to.eventually.have.nested.property('invitations.guilds') - .to.not.include({ id: publicGuild._id }); - }); - }); - context('Rejecting a private guild invite', () => { let invitedUser; let guild; @@ -54,6 +17,7 @@ describe('POST /group/:groupId/reject-invite', () => { privacy: 'private', }, invites: 1, + upgradeToGroupPlan: true, }); guild = group; 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 c8a903d891..6e8e7822e1 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 @@ -25,6 +25,7 @@ describe('POST /groups/:groupId/removeMember/:memberId', () => { }, invites: 1, members: 2, + upgradeToGroupPlan: true, }); guild = group; @@ -129,9 +130,11 @@ describe('POST /groups/:groupId/removeMember/:memberId', () => { it('sends email to removed user', async () => { await leader.post(`/groups/${guild._id}/removeMember/${member._id}`); - expect(email.sendTxn).to.be.calledOnce; + expect(email.sendTxn).to.be.calledTwice; expect(email.sendTxn.args[0][0]._id).to.eql(member._id); expect(email.sendTxn.args[0][1]).to.eql('kicked-from-guild'); + expect(email.sendTxn.args[1][0]._id).to.eql(member._id); + expect(email.sendTxn.args[1][1]).to.eql('group-member-removed'); }); }); 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 179e6dfdee..aab4c50a27 100644 --- a/test/api/v3/integration/groups/POST-groups_invite.test.js +++ b/test/api/v3/integration/groups/POST-groups_invite.test.js @@ -3,24 +3,23 @@ import nconf from 'nconf'; import { createAndPopulateGroup, generateUser, - generateGroup, translate as t, } from '../../../../helpers/api-integration/v3'; const INVITES_LIMIT = 100; -const PARTY_LIMIT_MEMBERS = 29; +const PARTY_LIMIT_MEMBERS = 30; const MAX_EMAIL_INVITES_BY_USER = 200; describe('Post /groups/:groupId/invite', () => { let inviter; let group; - const groupName = 'Test Public Guild'; + const groupName = 'Test Party'; beforeEach(async () => { inviter = await generateUser({ balance: 4 }); group = await inviter.post('/groups', { name: groupName, - type: 'guild', + type: 'party', }); }); @@ -65,45 +64,44 @@ describe('Post /groups/:groupId/invite', () => { it('invites a user to a group by username', async () => { const userToInvite = await generateUser(); - await expect(inviter.post(`/groups/${group._id}/invite`, { + const response = await inviter.post(`/groups/${group._id}/invite`, { usernames: [userToInvite.auth.local.lowerCaseUsername], - })).to.eventually.deep.equal([{ - id: group._id, - name: groupName, - inviter: inviter._id, - publicGuild: false, - }]); + }); + expect(response).to.be.an('Array'); + expect(response[0]).to.have.all.keys(['_id', 'id', 'name', 'inviter']); + expect(response[0]._id).to.be.a('String'); + expect(response[0].id).to.eql(group._id); + expect(response[0].name).to.eql(groupName); + expect(response[0].inviter).to.eql(inviter._id); await expect(userToInvite.get('/user')) - .to.eventually.have.nested.property('invitations.guilds[0].id', group._id); + .to.eventually.have.nested.property('invitations.parties[0].id', group._id); }); it('invites multiple users to a group by uuid', async () => { const userToInvite = await generateUser(); const userToInvite2 = await generateUser(); - await expect(inviter.post(`/groups/${group._id}/invite`, { + const response = await (inviter.post(`/groups/${group._id}/invite`, { usernames: [ userToInvite.auth.local.lowerCaseUsername, userToInvite2.auth.local.lowerCaseUsername, ], - })).to.eventually.deep.equal([ - { - id: group._id, - name: groupName, - inviter: inviter._id, - publicGuild: false, - }, - { - id: group._id, - name: groupName, - inviter: inviter._id, - publicGuild: false, - }, - ]); + })); + expect(response).to.be.an('Array'); + expect(response[0]).to.have.all.keys(['_id', 'id', 'name', 'inviter']); + expect(response[0]._id).to.be.a('String'); + expect(response[0].id).to.eql(group._id); + expect(response[0].name).to.eql(groupName); + expect(response[0].inviter).to.eql(inviter._id); + expect(response[1]).to.have.all.keys(['_id', 'id', 'name', 'inviter']); + expect(response[1]._id).to.be.a('String'); + expect(response[1].id).to.eql(group._id); + expect(response[1].name).to.eql(groupName); + expect(response[1].inviter).to.eql(inviter._id); - await expect(userToInvite.get('/user')).to.eventually.have.nested.property('invitations.guilds[0].id', group._id); - await expect(userToInvite2.get('/user')).to.eventually.have.nested.property('invitations.guilds[0].id', group._id); + await expect(userToInvite.get('/user')).to.eventually.have.nested.property('invitations.parties[0].id', group._id); + await expect(userToInvite2.get('/user')).to.eventually.have.nested.property('invitations.parties[0].id', group._id); }); }); @@ -214,42 +212,42 @@ describe('Post /groups/:groupId/invite', () => { it('invites a user to a group by uuid', async () => { const userToInvite = await generateUser(); - await expect(inviter.post(`/groups/${group._id}/invite`, { + const response = await inviter.post(`/groups/${group._id}/invite`, { uuids: [userToInvite._id], - })).to.eventually.deep.equal([{ - id: group._id, - name: groupName, - inviter: inviter._id, - publicGuild: false, - }]); + }); + expect(response).to.be.an('Array'); + expect(response[0]).to.have.all.keys(['_id', 'id', 'name', 'inviter']); + expect(response[0]._id).to.be.a('String'); + expect(response[0].id).to.eql(group._id); + expect(response[0].name).to.eql(groupName); + expect(response[0].inviter).to.eql(inviter._id); await expect(userToInvite.get('/user')) - .to.eventually.have.nested.property('invitations.guilds[0].id', group._id); + .to.eventually.have.nested.property('invitations.parties[0].id', group._id); }); it('invites multiple users to a group by uuid', async () => { const userToInvite = await generateUser(); const userToInvite2 = await generateUser(); - await expect(inviter.post(`/groups/${group._id}/invite`, { + const response = await inviter.post(`/groups/${group._id}/invite`, { uuids: [userToInvite._id, userToInvite2._id], - })).to.eventually.deep.equal([ - { - id: group._id, - name: groupName, - inviter: inviter._id, - publicGuild: false, - }, - { - id: group._id, - name: groupName, - inviter: inviter._id, - publicGuild: false, - }, - ]); + }); - await expect(userToInvite.get('/user')).to.eventually.have.nested.property('invitations.guilds[0].id', group._id); - await expect(userToInvite2.get('/user')).to.eventually.have.nested.property('invitations.guilds[0].id', group._id); + expect(response).to.be.an('Array'); + expect(response[0]).to.have.all.keys(['_id', 'id', 'name', 'inviter']); + expect(response[0]._id).to.be.a('String'); + expect(response[0].id).to.eql(group._id); + expect(response[0].name).to.eql(groupName); + expect(response[0].inviter).to.eql(inviter._id); + expect(response[1]).to.have.all.keys(['_id', 'id', 'name', 'inviter']); + expect(response[1]._id).to.be.a('String'); + expect(response[1].id).to.eql(group._id); + expect(response[1].name).to.eql(groupName); + expect(response[1].inviter).to.eql(inviter._id); + + await expect(userToInvite.get('/user')).to.eventually.have.nested.property('invitations.parties[0].id', group._id); + await expect(userToInvite2.get('/user')).to.eventually.have.nested.property('invitations.parties[0].id', group._id); }); it('returns an error when inviting multiple users and a user is not found', async () => { @@ -338,12 +336,8 @@ describe('Post /groups/:groupId/invite', () => { invitesSent: MAX_EMAIL_INVITES_BY_USER, balance: 4, }); - const tmpGroup = await inviterWithMax.post('/groups', { - name: groupName, - type: 'guild', - }); - await expect(inviterWithMax.post(`/groups/${tmpGroup._id}/invite`, { + await expect(inviterWithMax.post(`/groups/${group._id}/invite`, { emails: [testInvite], inviter: 'inviter name', })) @@ -419,15 +413,15 @@ describe('Post /groups/:groupId/invite', () => { }); const invitedUser = await newUser.get('/user'); - expect(invitedUser.invitations.guilds[0].id).to.equal(group._id); + expect(invitedUser.invitations.parties[0].id).to.equal(group._id); expect(invite).to.exist; }); - it('invites marks invite with cancelled plan', async () => { - const cancelledPlanGroup = await generateGroup(inviter, { - type: 'guild', - name: generateUUID(), - }); + it('invites user to group with cancelled plan', async () => { + let cancelledPlanGroup; + ({ group: cancelledPlanGroup, groupLeader: inviter } = await createAndPopulateGroup({ + upgradeToGroupPlan: true, + })); await cancelledPlanGroup.createCancelledSubscription(); const newUser = await generateUser(); @@ -437,13 +431,13 @@ describe('Post /groups/:groupId/invite', () => { }); const invitedUser = await newUser.get('/user'); - expect(invitedUser.invitations.guilds[0].id).to.equal(cancelledPlanGroup._id); - expect(invitedUser.invitations.guilds[0].cancelledPlan).to.be.true; + expect(invitedUser.invitations.parties[0].id).to.equal(cancelledPlanGroup._id); + expect(invitedUser.invitations.parties[0].cancelledPlan).to.be.true; expect(invite).to.exist; }); }); - describe('guild invites', () => { + describe('party invites', () => { it('returns an error when inviter has no chat privileges', async () => { const inviterMuted = await inviter.update({ 'flags.chatRevoked': true }); const userToInvite = await generateUser(); @@ -457,103 +451,13 @@ describe('Post /groups/:groupId/invite', () => { }); }); - it('returns an error when invited user is already invited to the group', async () => { - const userToInvite = await generateUser(); - await inviter.post(`/groups/${group._id}/invite`, { - uuids: [userToInvite._id], - }); - - await expect(inviter.post(`/groups/${group._id}/invite`, { - uuids: [userToInvite._id], - })) - .to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('userAlreadyInvitedToGroup', { userId: userToInvite._id, username: userToInvite.profile.name }), - }); - }); - - it('returns an error when invited user is already in the group', async () => { - const userToInvite = await generateUser(); - await inviter.post(`/groups/${group._id}/invite`, { - uuids: [userToInvite._id], - }); - await userToInvite.post(`/groups/${group._id}/join`); - - await expect(inviter.post(`/groups/${group._id}/invite`, { - uuids: [userToInvite._id], - })) - .to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('userAlreadyInGroup', { userId: userToInvite._id, username: userToInvite.profile.name }), - }); - }); - - it('allows 30+ members in a guild', async () => { - const invitesToGenerate = []; - // Generate 30 users to invite (30 + leader = 31 members) - for (let i = 0; i < PARTY_LIMIT_MEMBERS; i += 1) { - invitesToGenerate.push(generateUser()); - } - const generatedInvites = await Promise.all(invitesToGenerate); - // Invite users - expect(await inviter.post(`/groups/${group._id}/invite`, { - uuids: generatedInvites.map(invite => invite._id), - })).to.be.an('array'); - }).timeout(10000); - - // @TODO: Add this after we are able to mock the group plan route - xit('returns an error when a non-leader invites to a group plan', async () => { - const userToInvite = await generateUser(); - - const nonGroupLeader = await generateUser(); - await inviter.post(`/groups/${group._id}/invite`, { - uuids: [nonGroupLeader._id], - }); - await nonGroupLeader.post(`/groups/${group._id}/join`); - - await expect(nonGroupLeader.post(`/groups/${group._id}/invite`, { - uuids: [userToInvite._id], - })) - .to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('onlyGroupLeaderCanInviteToGroupPlan'), - }); - }); - }); - - describe('party invites', () => { - let party; - - beforeEach(async () => { - party = await inviter.post('/groups', { - name: 'Test Party', - type: 'party', - }); - }); - - it('returns an error when inviter has no chat privileges', async () => { - const inviterMuted = await inviter.update({ 'flags.chatRevoked': true }); - const userToInvite = await generateUser(); - await expect(inviterMuted.post(`/groups/${party._id}/invite`, { - uuids: [userToInvite._id], - })) - .to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('chatPrivilegesRevoked'), - }); - }); - it('returns an error when invited user has a pending invitation to the party', async () => { const userToInvite = await generateUser(); - await inviter.post(`/groups/${party._id}/invite`, { + await inviter.post(`/groups/${group._id}/invite`, { uuids: [userToInvite._id], }); - await expect(inviter.post(`/groups/${party._id}/invite`, { + await expect(inviter.post(`/groups/${group._id}/invite`, { uuids: [userToInvite._id], })) .to.eventually.be.rejected.and.eql({ @@ -566,13 +470,13 @@ describe('Post /groups/:groupId/invite', () => { it('returns an error when invited user is already in a party of more than 1 member', async () => { const userToInvite = await generateUser(); const userToInvite2 = await generateUser(); - await inviter.post(`/groups/${party._id}/invite`, { + await inviter.post(`/groups/${group._id}/invite`, { uuids: [userToInvite._id, userToInvite2._id], }); - await userToInvite.post(`/groups/${party._id}/join`); - await userToInvite2.post(`/groups/${party._id}/join`); + await userToInvite.post(`/groups/${group._id}/join`); + await userToInvite2.post(`/groups/${group._id}/join`); - await expect(inviter.post(`/groups/${party._id}/invite`, { + await expect(inviter.post(`/groups/${group._id}/invite`, { uuids: [userToInvite._id], })) .to.eventually.be.rejected.and.eql({ @@ -596,7 +500,7 @@ describe('Post /groups/:groupId/invite', () => { }); // Invite to first party - await inviter.post(`/groups/${party._id}/invite`, { + await inviter.post(`/groups/${group._id}/invite`, { uuids: [userToInvite._id], }); @@ -609,28 +513,27 @@ describe('Post /groups/:groupId/invite', () => { const invitedUser = await userToInvite.get('/user'); expect(invitedUser.invitations.parties.length).to.equal(2); - expect(invitedUser.invitations.parties[0].id).to.equal(party._id); + expect(invitedUser.invitations.parties[0].id).to.equal(group._id); expect(invitedUser.invitations.parties[1].id).to.equal(party2._id); }); - it('allow inviting a user if party id is not associated with a real party', async () => { + it('allows inviting a user if party id is not associated with a real party', async () => { const userToInvite = await generateUser({ party: { _id: generateUUID() }, }); - await inviter.post(`/groups/${party._id}/invite`, { + await inviter.post(`/groups/${group._id}/invite`, { uuids: [userToInvite._id], }); - expect((await userToInvite.get('/user')).invitations.parties[0].id).to.equal(party._id); + expect((await userToInvite.get('/user')).invitations.parties[0].id).to.equal(group._id); }); }); describe('party size limits', () => { - let party; let partyLeader; beforeEach(async () => { - group = await createAndPopulateGroup({ + ({ group, groupLeader: partyLeader } = await createAndPopulateGroup({ groupDetails: { name: 'Test Party', type: 'party', @@ -638,9 +541,7 @@ describe('Post /groups/:groupId/invite', () => { }, // Generate party with 20 members members: PARTY_LIMIT_MEMBERS - 10, - }); - party = group.group; - partyLeader = group.groupLeader; + })); }); it('allows 30 members in a party', async () => { @@ -651,7 +552,7 @@ describe('Post /groups/:groupId/invite', () => { } const generatedInvites = await Promise.all(invitesToGenerate); // Invite users - expect(await partyLeader.post(`/groups/${party._id}/invite`, { + expect(await partyLeader.post(`/groups/${group._id}/invite`, { uuids: generatedInvites.map(invite => invite._id), })).to.be.an('array'); }).timeout(10000); @@ -664,13 +565,13 @@ describe('Post /groups/:groupId/invite', () => { } const generatedInvites = await Promise.all(invitesToGenerate); // Invite users - await expect(partyLeader.post(`/groups/${party._id}/invite`, { + await expect(partyLeader.post(`/groups/${group._id}/invite`, { uuids: generatedInvites.map(invite => invite._id), })) .to.eventually.be.rejected.and.eql({ code: 400, error: 'BadRequest', - message: t('partyExceedsMembersLimit', { maxMembersParty: PARTY_LIMIT_MEMBERS + 1 }), + message: t('partyExceedsMembersLimit', { maxMembersParty: PARTY_LIMIT_MEMBERS }), }); }).timeout(10000); }); diff --git a/test/api/v3/integration/groups/POST-groups_manager.test.js b/test/api/v3/integration/groups/POST-groups_manager.test.js index e7b6cad9de..c191cc4d17 100644 --- a/test/api/v3/integration/groups/POST-groups_manager.test.js +++ b/test/api/v3/integration/groups/POST-groups_manager.test.js @@ -17,9 +17,10 @@ describe('POST /group/:groupId/add-manager', () => { groupDetails: { name: groupName, type: groupType, - privacy: 'public', + privacy: 'private', }, members: 1, + upgradeToGroupPlan: true, }); groupToUpdate = group; diff --git a/test/api/v3/integration/groups/PUT-groups.test.js b/test/api/v3/integration/groups/PUT-groups.test.js index 77d4ad6487..e584703fea 100644 --- a/test/api/v3/integration/groups/PUT-groups.test.js +++ b/test/api/v3/integration/groups/PUT-groups.test.js @@ -23,10 +23,11 @@ describe('PUT /group', () => { groupDetails: { name: groupName, type: groupType, - privacy: 'public', + privacy: 'private', categories: groupCategories, }, members: 1, + upgradeToGroupPlan: true, }); adminUser = await generateUser({ 'permissions.moderator': true }); groupToUpdate = group; @@ -106,14 +107,28 @@ describe('PUT /group', () => { expect(updatedGroup.name).to.equal(groupUpdatedName); }); - it('allows a leader to change leaders', async () => { - const updatedGroup = await leader.put(`/groups/${groupToUpdate._id}`, { + it('does not allow a leader to change leader of active group plan', async () => { + await expect(leader.put(`/groups/${groupToUpdate._id}`, { name: groupUpdatedName, leader: nonLeader._id, + })).to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('cannotChangeLeaderWithActiveGroupPlan'), + }); + }); + + it('allows a leader of a party to change leaders', async () => { + const { group: party, groupLeader: partyLeader, members } = await createAndPopulateGroup({ + members: 1, + }); + const updatedGroup = await partyLeader.put(`/groups/${party._id}`, { + name: groupUpdatedName, + leader: members[0]._id, }); - expect(updatedGroup.leader._id).to.eql(nonLeader._id); - expect(updatedGroup.leader.profile.name).to.eql(nonLeader.profile.name); + expect(updatedGroup.leader._id).to.eql(members[0]._id); + expect(updatedGroup.leader.profile.name).to.eql(members[0].profile.name); expect(updatedGroup.name).to.equal(groupUpdatedName); }); @@ -122,15 +137,16 @@ describe('PUT /group', () => { groupDetails: { name: 'public guild', type: 'guild', - privacy: 'public', + privacy: 'private', }, + upgradeToGroupPlan: true, }); const updateGroupDetails = { id: group._id, name: 'public guild', type: 'guild', - privacy: 'public', + privacy: 'private', bannedWordsAllowed: true, }; @@ -150,9 +166,11 @@ describe('PUT /group', () => { groupDetails: { name: 'public guild', type: 'guild', - privacy: 'public', + privacy: 'private', }, + upgradeToGroupPlan: true, }); + await groupLeader.update({ permissions: {} }); const updateGroupDetails = { id: group._id, diff --git a/test/api/v3/integration/payments/amazon/GET-payments_amazon_subscribe_cancel.test.js b/test/api/v3/integration/payments/amazon/GET-payments_amazon_subscribe_cancel.test.js index 67313693be..7c2466f0f2 100644 --- a/test/api/v3/integration/payments/amazon/GET-payments_amazon_subscribe_cancel.test.js +++ b/test/api/v3/integration/payments/amazon/GET-payments_amazon_subscribe_cancel.test.js @@ -1,6 +1,6 @@ import { + createAndPopulateGroup, generateUser, - generateGroup, translate as t, } from '../../../../../helpers/api-integration/v3'; import amzLib from '../../../../../../website/server/libs/payments/amazon'; @@ -50,22 +50,21 @@ describe('payments : amazon #subscribeCancel', () => { }); it('cancels a group subscription', async () => { - user = await generateUser({ - 'profile.name': 'sender', - 'purchased.plan.customerId': 'customer-id', - 'purchased.plan.planId': 'basic_3mo', - 'purchased.plan.lastBillingDate': new Date(), - balance: 2, - }); - - group = await generateGroup(user, { - name: 'test group', - type: 'guild', - privacy: 'public', - 'purchased.plan.customerId': 'customer-id', - 'purchased.plan.planId': 'basic_3mo', - 'purchased.plan.lastBillingDate': new Date(), - }); + ({ group, groupLeader: user } = await createAndPopulateGroup({ + groupDetails: { + name: 'test group', + type: 'guild', + privacy: 'private', + }, + leaderDetails: { + 'profile.name': 'sender', + 'purchased.plan.customerId': 'customer-id', + 'purchased.plan.planId': 'basic_3mo', + 'purchased.plan.lastBillingDate': new Date(), + balance: 2, + }, + upgradeToGroupPlan: true, + })); await user.get(`${endpoint}&groupId=${group._id}`); diff --git a/test/api/v3/integration/payments/amazon/POST-payments_amazon_subscribe.test.js b/test/api/v3/integration/payments/amazon/POST-payments_amazon_subscribe.test.js index b6c09848a3..3ce1a718e2 100644 --- a/test/api/v3/integration/payments/amazon/POST-payments_amazon_subscribe.test.js +++ b/test/api/v3/integration/payments/amazon/POST-payments_amazon_subscribe.test.js @@ -70,8 +70,8 @@ describe('payments - amazon - #subscribe', () => { group = await generateGroup(user, { name: 'test group', - type: 'guild', - privacy: 'public', + type: 'party', + privacy: 'private', 'purchased.plan.customerId': 'customer-id', 'purchased.plan.planId': 'basic_3mo', 'purchased.plan.lastBillingDate': new Date(), diff --git a/test/api/v3/integration/payments/stripe/GET-payments_stripe_subscribe_cancel.test.js b/test/api/v3/integration/payments/stripe/GET-payments_stripe_subscribe_cancel.test.js index d6ec1e0312..77098a1b46 100644 --- a/test/api/v3/integration/payments/stripe/GET-payments_stripe_subscribe_cancel.test.js +++ b/test/api/v3/integration/payments/stripe/GET-payments_stripe_subscribe_cancel.test.js @@ -1,7 +1,7 @@ import { generateUser, - generateGroup, translate as t, + createAndPopulateGroup, } from '../../../../../helpers/api-integration/v3'; import stripePayments from '../../../../../../website/server/libs/payments/stripe'; @@ -48,22 +48,21 @@ describe('payments - stripe - #subscribeCancel', () => { }); it('cancels a group subscription', async () => { - user = await generateUser({ - 'profile.name': 'sender', - 'purchased.plan.customerId': 'customer-id', - 'purchased.plan.planId': 'basic_3mo', - 'purchased.plan.lastBillingDate': new Date(), - balance: 2, - }); - - group = await generateGroup(user, { - name: 'test group', - type: 'guild', - privacy: 'public', - 'purchased.plan.customerId': 'customer-id', - 'purchased.plan.planId': 'basic_3mo', - 'purchased.plan.lastBillingDate': new Date(), - }); + ({ group, groupLeader: user } = await createAndPopulateGroup({ + groupDetails: { + name: 'test group', + type: 'guild', + privacy: 'private', + }, + leaderDetails: { + 'profile.name': 'sender', + 'purchased.plan.customerId': 'customer-id', + 'purchased.plan.planId': 'basic_3mo', + 'purchased.plan.lastBillingDate': new Date(), + balance: 2, + }, + upgradeToGroupPlan: true, + })); await user.get(`${endpoint}&groupId=${group._id}`); diff --git a/test/api/v3/integration/quests/POST-groups_groupId_quests_accept.test.js b/test/api/v3/integration/quests/POST-groups_groupId_quests_accept.test.js index deb105de7c..671ff002d4 100644 --- a/test/api/v3/integration/quests/POST-groups_groupId_quests_accept.test.js +++ b/test/api/v3/integration/quests/POST-groups_groupId_quests_accept.test.js @@ -53,6 +53,7 @@ describe('POST /groups/:groupId/quests/accept', () => { it('does not accept quest for a guild', async () => { const { group: guild, groupLeader: guildLeader } = await createAndPopulateGroup({ groupDetails: { type: 'guild', privacy: 'private' }, + upgradeToGroupPlan: true, }); await expect(guildLeader.post(`/groups/${guild._id}/quests/accept`)) diff --git a/test/api/v3/integration/quests/POST-groups_groupId_quests_force-start.test.js b/test/api/v3/integration/quests/POST-groups_groupId_quests_force-start.test.js index 2769b6c00f..48c9378105 100644 --- a/test/api/v3/integration/quests/POST-groups_groupId_quests_force-start.test.js +++ b/test/api/v3/integration/quests/POST-groups_groupId_quests_force-start.test.js @@ -43,6 +43,7 @@ describe('POST /groups/:groupId/quests/force-start', () => { it('does not force start quest for a guild', async () => { const { group: guild, groupLeader: guildLeader } = await createAndPopulateGroup({ groupDetails: { type: 'guild', privacy: 'private' }, + upgradeToGroupPlan: true, }); await expect(guildLeader.post(`/groups/${guild._id}/quests/force-start`)) diff --git a/test/api/v3/integration/quests/POST-groups_groupId_quests_invite.test.js b/test/api/v3/integration/quests/POST-groups_groupId_quests_invite.test.js index 547cbd44ae..e7dd9426b0 100644 --- a/test/api/v3/integration/quests/POST-groups_groupId_quests_invite.test.js +++ b/test/api/v3/integration/quests/POST-groups_groupId_quests_invite.test.js @@ -51,14 +51,13 @@ describe('POST /groups/:groupId/quests/invite/:questKey', () => { }); it('does not issue invites for Guilds', async () => { - const { group } = await createAndPopulateGroup({ - groupDetails: { type: 'guild', privacy: 'public' }, + const { group, groupLeader } = await createAndPopulateGroup({ + groupDetails: { type: 'guild', privacy: 'private' }, members: 1, + upgradeToGroupPlan: true, }); - const alternateGroup = group; - - await expect(leader.post(`/groups/${alternateGroup._id}/quests/invite/${PET_QUEST}`)).to.eventually.be.rejected.and.eql({ + await expect(groupLeader.post(`/groups/${group._id}/quests/invite/${PET_QUEST}`)).to.eventually.be.rejected.and.eql({ code: 401, error: 'NotAuthorized', message: t('guildQuestsNotSupported'), diff --git a/test/api/v3/integration/quests/POST-groups_groupid_quests_abort.test.js b/test/api/v3/integration/quests/POST-groups_groupid_quests_abort.test.js index 7a42166a2c..3e1bf567bd 100644 --- a/test/api/v3/integration/quests/POST-groups_groupid_quests_abort.test.js +++ b/test/api/v3/integration/quests/POST-groups_groupid_quests_abort.test.js @@ -52,6 +52,7 @@ describe('POST /groups/:groupId/quests/abort', () => { it('returns an error when group is a guild', async () => { const { group: guild, groupLeader: guildLeader } = await createAndPopulateGroup({ groupDetails: { type: 'guild', privacy: 'private' }, + upgradeToGroupPlan: true, }); await expect(guildLeader.post(`/groups/${guild._id}/quests/abort`)) diff --git a/test/api/v3/integration/quests/POST-groups_groupid_quests_cancel.test.js b/test/api/v3/integration/quests/POST-groups_groupid_quests_cancel.test.js index 7df6dbd0f4..77c5a5ec17 100644 --- a/test/api/v3/integration/quests/POST-groups_groupid_quests_cancel.test.js +++ b/test/api/v3/integration/quests/POST-groups_groupid_quests_cancel.test.js @@ -52,6 +52,7 @@ describe('POST /groups/:groupId/quests/cancel', () => { it('returns an error when group is a guild', async () => { const { group: guild, groupLeader: guildLeader } = await createAndPopulateGroup({ groupDetails: { type: 'guild', privacy: 'private' }, + upgradeToGroupPlan: true, }); await expect(guildLeader.post(`/groups/${guild._id}/quests/cancel`)) diff --git a/test/api/v3/integration/quests/POST-groups_groupid_quests_leave.test.js b/test/api/v3/integration/quests/POST-groups_groupid_quests_leave.test.js index 1e2f7d6983..87d37464c1 100644 --- a/test/api/v3/integration/quests/POST-groups_groupid_quests_leave.test.js +++ b/test/api/v3/integration/quests/POST-groups_groupid_quests_leave.test.js @@ -51,6 +51,7 @@ describe('POST /groups/:groupId/quests/leave', () => { it('returns an error when group is a guild', async () => { const { group: guild, groupLeader: guildLeader } = await createAndPopulateGroup({ groupDetails: { type: 'guild', privacy: 'private' }, + upgradeToGroupPlan: true, }); await expect(guildLeader.post(`/groups/${guild._id}/quests/leave`)) diff --git a/test/api/v3/integration/quests/POST-groups_groupid_quests_reject.test.js b/test/api/v3/integration/quests/POST-groups_groupid_quests_reject.test.js index 4f897d2b2b..e956c71664 100644 --- a/test/api/v3/integration/quests/POST-groups_groupid_quests_reject.test.js +++ b/test/api/v3/integration/quests/POST-groups_groupid_quests_reject.test.js @@ -53,6 +53,7 @@ describe('POST /groups/:groupId/quests/reject', () => { it('returns an error when group is a guild', async () => { const { group: guild, groupLeader: guildLeader } = await createAndPopulateGroup({ groupDetails: { type: 'guild', privacy: 'private' }, + upgradeToGroupPlan: true, }); await expect(guildLeader.post(`/groups/${guild._id}/quests/reject`)) diff --git a/test/api/v3/integration/tasks/groups/POST-tasks_move_taskId_to_position.test.js b/test/api/v3/integration/tasks/groups/POST-tasks_move_taskId_to_position.test.js index 3255f85206..113a2f3f3c 100644 --- a/test/api/v3/integration/tasks/groups/POST-tasks_move_taskId_to_position.test.js +++ b/test/api/v3/integration/tasks/groups/POST-tasks_move_taskId_to_position.test.js @@ -1,6 +1,5 @@ import { - generateUser, - generateGroup, + createAndPopulateGroup, } from '../../../../../helpers/api-integration/v3'; describe('POST group-tasks/:taskId/move/to/:position', () => { @@ -8,8 +7,12 @@ describe('POST group-tasks/:taskId/move/to/:position', () => { guild; beforeEach(async () => { - user = await generateUser({ balance: 1 }); - guild = await generateGroup(user, { type: 'guild' }, { 'purchased.plan.customerId': 'group-unlimited' }); + const { group, groupLeader } = await createAndPopulateGroup({ + groupDetails: { type: 'guild', privacy: 'private' }, + upgradeToGroupPlan: true, + }); + guild = group; + user = groupLeader; }); it('can move task to new position', async () => { diff --git a/test/api/v3/integration/user/DELETE-user.test.js b/test/api/v3/integration/user/DELETE-user.test.js index 5c176fe141..f0dd2018c6 100644 --- a/test/api/v3/integration/user/DELETE-user.test.js +++ b/test/api/v3/integration/user/DELETE-user.test.js @@ -1,5 +1,4 @@ import { - find, each, map, } from 'lodash'; @@ -198,95 +197,6 @@ describe('DELETE /user', () => { await expect(checkExistence('party', party._id)).to.eventually.eql(false); }); }); - - context('last member of a private guild', () => { - let privateGuild; - - beforeEach(async () => { - privateGuild = await generateGroup(user, { - type: 'guild', - privacy: 'private', - }); - }); - - it('deletes guild when user is the only member', async () => { - await user.del('/user', { - password, - }); - await expect(checkExistence('groups', privateGuild._id)).to.eventually.eql(false); - }); - }); - - context('groups user is leader of', () => { - let guild; let oldLeader; let - newLeader; - - beforeEach(async () => { - const { group, groupLeader, members } = await createAndPopulateGroup({ - groupDetails: { - type: 'guild', - privacy: 'public', - }, - members: 1, - }); - - guild = group; - newLeader = members[0]; // eslint-disable-line prefer-destructuring - oldLeader = groupLeader; - }); - - it('chooses new group leader for any group user was the leader of', async () => { - await oldLeader.del('/user', { - password, - }); - - const updatedGuild = await newLeader.get(`/groups/${guild._id}`); - - expect(updatedGuild.leader).to.exist; - expect(updatedGuild.leader._id).to.not.eql(oldLeader._id); - }); - }); - - context('groups user is a part of', () => { - let group1; let group2; let userToDelete; let - otherUser; - - beforeEach(async () => { - userToDelete = await generateUser({ balance: 10 }); - - group1 = await generateGroup(userToDelete, { - type: 'guild', - privacy: 'public', - }); - - const { group, members } = await createAndPopulateGroup({ - groupDetails: { - type: 'guild', - privacy: 'public', - }, - members: 3, - }); - - group2 = group; - otherUser = members[0]; // eslint-disable-line prefer-destructuring - - await userToDelete.post(`/groups/${group2._id}/join`); - }); - - it('removes user from all groups user was a part of', async () => { - await userToDelete.del('/user', { - password, - }); - - const updatedGroup1Members = await otherUser.get(`/groups/${group1._id}/members`); - const updatedGroup2Members = await otherUser.get(`/groups/${group2._id}/members`); - const userInGroup = find(updatedGroup2Members, member => member._id === userToDelete._id); - - expect(updatedGroup1Members).to.be.empty; - expect(updatedGroup2Members).to.not.be.empty; - expect(userInGroup).to.not.exist; - }); - }); }); context('user with Google auth', async () => { diff --git a/test/api/v3/integration/user/POST-user_purchase.test.js b/test/api/v3/integration/user/POST-user_purchase.test.js index 502bcb3790..0fd8871d2b 100644 --- a/test/api/v3/integration/user/POST-user_purchase.test.js +++ b/test/api/v3/integration/user/POST-user_purchase.test.js @@ -51,6 +51,7 @@ describe('POST /user/purchase/:type/:key', () => { type: 'guild', privacy: 'private', }, + upgradeToGroupPlan: true, }); await group.update({ 'leaderOnly.getGems': true, @@ -77,6 +78,7 @@ describe('POST /user/purchase/:type/:key', () => { privacy: 'private', }, members: 1, + upgradeToGroupPlan: true, }); await group.update({ 'leaderOnly.getGems': true, diff --git a/test/api/v3/integration/user/auth/POST-register_local.test.js b/test/api/v3/integration/user/auth/POST-register_local.test.js index 6e548051cd..d97751fd91 100644 --- a/test/api/v3/integration/user/auth/POST-register_local.test.js +++ b/test/api/v3/integration/user/auth/POST-register_local.test.js @@ -714,31 +714,6 @@ describe('POST /user/auth/local/register', () => { expect(user.invitations.party).to.eql({}); }); - - it('adds a user to a guild on an invite of type other than party', async () => { - const { group, groupLeader } = await createAndPopulateGroup({ - groupDetails: { type: 'guild', privacy: 'private' }, - }); - - const invite = encrypt(JSON.stringify({ - id: group._id, - inviter: groupLeader._id, - sentAt: Date.now(), - })); - - const user = await api.post(`/user/auth/local/register?groupInvite=${invite}`, { - username, - email, - password, - confirmPassword: password, - }); - - expect(user.invitations.guilds[0]).to.eql({ - id: group._id, - name: group.name, - inviter: groupLeader._id, - }); - }); }); context('successful login via api', () => { diff --git a/test/api/v4/user/auth/POST-register_local.test.js b/test/api/v4/user/auth/POST-register_local.test.js index 919b650e25..11836cd213 100644 --- a/test/api/v4/user/auth/POST-register_local.test.js +++ b/test/api/v4/user/auth/POST-register_local.test.js @@ -665,6 +665,7 @@ describe('POST /user/auth/local/register', () => { it('adds a user to a guild on an invite of type other than party', async () => { const { group, groupLeader } = await createAndPopulateGroup({ groupDetails: { type: 'guild', privacy: 'private' }, + upgradeToGroupPlan: true, }); const invite = encrypt(JSON.stringify({ diff --git a/test/helpers/api-integration/v3/object-generators.js b/test/helpers/api-integration/v3/object-generators.js index b48dd862a9..e3a7c723c9 100644 --- a/test/helpers/api-integration/v3/object-generators.js +++ b/test/helpers/api-integration/v3/object-generators.js @@ -127,6 +127,9 @@ export async function createAndPopulateGroup (settings = {}) { const upgradeToGroupPlan = settings.upgradeToGroupPlan || false; const { groupDetails } = settings; const leaderDetails = settings.leaderDetails || { balance: 10 }; + if (upgradeToGroupPlan) { + leaderDetails.permissions = { fullAccess: true }; + } const groupLeader = await generateUser(leaderDetails); const group = await generateGroup(groupLeader, groupDetails); diff --git a/test/helpers/api-integration/v4/object-generators.js b/test/helpers/api-integration/v4/object-generators.js index 59b0642b15..92a4c50748 100644 --- a/test/helpers/api-integration/v4/object-generators.js +++ b/test/helpers/api-integration/v4/object-generators.js @@ -120,6 +120,9 @@ export async function createAndPopulateGroup (settings = {}) { const upgradeToGroupPlan = settings.upgradeToGroupPlan || false; const { groupDetails } = settings; const leaderDetails = settings.leaderDetails || { balance: 10 }; + if (upgradeToGroupPlan) { + leaderDetails.permissions = { fullAccess: true }; + } const groupLeader = await generateUser(leaderDetails); const group = await generateGroup(groupLeader, groupDetails); diff --git a/website/client/package-lock.json b/website/client/package-lock.json index f04b688bba..93f855e2a4 100644 --- a/website/client/package-lock.json +++ b/website/client/package-lock.json @@ -13318,31 +13318,11 @@ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, - "emojis-list": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", - "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==" - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, "is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" }, - "loader-utils": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", - "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - } - }, "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", @@ -13374,35 +13354,6 @@ "ansi-regex": "^5.0.1" } }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - }, - "vue-loader-v16": { - "version": "npm:vue-loader@16.8.3", - "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-16.8.3.tgz", - "integrity": "sha512-7vKN45IxsKxe5GcVCbc2qFU5aWzyiLrYJyUuMz4BQLKctCj/fmCa0w6fGiiQ2cLFetNcek1ppGJQDCup0c1hpA==", - "requires": { - "chalk": "^4.1.0", - "hash-sum": "^2.0.0", - "loader-utils": "^2.0.0" - }, - "dependencies": { - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - } - } - }, "wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", @@ -16901,9 +16852,9 @@ } }, "core-js": { - "version": "3.31.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.31.0.tgz", - "integrity": "sha512-NIp2TQSGfR6ba5aalZD+ZQ1fSxGhDo/s1w0nx3RYzf2pnJxt7YynxFlFScP6eV7+GZsKO95NSjGxyJsU3DZgeQ==" + "version": "3.32.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.32.1.tgz", + "integrity": "sha512-lqufgNn9NLnESg5mQeYsxQP5w7wrViSj0jr/kv6ECQiByzQkrn1MKvV0L3acttpDqfQrHLwr2KCMgX5b8X+lyQ==" }, "core-js-compat": { "version": "3.11.0", @@ -21436,9 +21387,9 @@ "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==" }, "intro.js": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/intro.js/-/intro.js-7.0.1.tgz", - "integrity": "sha512-1oqz6aOz9cGQ3CrtVYhCSo6AkjnXUn302kcIWLaZ3TI4kKssRXDwDSz4VRoGcfC1jN+WfaSJXRBrITz+QVEBzg==" + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/intro.js/-/intro.js-7.2.0.tgz", + "integrity": "sha512-qbMfaB70rOXVBceIWNYnYTpVTiZsvQh/MIkfdQbpA9di9VBfj1GigUPfcCv3aOfsbrtPcri8vTLTA4FcEDcHSQ==" }, "invariant": { "version": "2.2.4", @@ -27802,9 +27753,9 @@ } }, "smartbanner.js": { - "version": "1.19.2", - "resolved": "https://registry.npmjs.org/smartbanner.js/-/smartbanner.js-1.19.2.tgz", - "integrity": "sha512-hwcGNp5Hza5PJHTmqP6H8q0XBYhloIQyJgdzv0ldz3HQSeEuKB2riVraQXdKuquE6ZU/0M0yubno53xJ/ZiQQg==" + "version": "1.19.3", + "resolved": "https://registry.npmjs.org/smartbanner.js/-/smartbanner.js-1.19.3.tgz", + "integrity": "sha512-30JaYaPHO0VRC8MXGeUGWm1jF3+kCwKgV2GW9uLa8J3zOuv9D9ZhZo0IKf/xIcX0HQBRisOU4RPhEj7UrNt8Sw==" }, "snapdragon": { "version": "0.8.2", @@ -30630,6 +30581,76 @@ } } }, + "vue-loader-v16": { + "version": "npm:vue-loader@16.8.3", + "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-16.8.3.tgz", + "integrity": "sha512-7vKN45IxsKxe5GcVCbc2qFU5aWzyiLrYJyUuMz4BQLKctCj/fmCa0w6fGiiQ2cLFetNcek1ppGJQDCup0c1hpA==", + "requires": { + "chalk": "^4.1.0", + "hash-sum": "^2.0.0", + "loader-utils": "^2.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "loader-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "vue-mugen-scroll": { "version": "0.2.6", "resolved": "https://registry.npmjs.org/vue-mugen-scroll/-/vue-mugen-scroll-0.2.6.tgz", @@ -31340,9 +31361,9 @@ } }, "word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==" + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.4.tgz", + "integrity": "sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==" }, "wordwrap": { "version": "1.0.0", diff --git a/website/client/package.json b/website/client/package.json index 329074b24c..33085fe263 100644 --- a/website/client/package.json +++ b/website/client/package.json @@ -32,7 +32,7 @@ "bootstrap": "^4.6.0", "bootstrap-vue": "^2.23.1", "chai": "^4.3.7", - "core-js": "^3.31.0", + "core-js": "^3.32.1", "dompurify": "^3.0.3", "eslint": "^6.8.0", "eslint-config-habitrpg": "^6.2.0", @@ -41,14 +41,14 @@ "habitica-markdown": "^3.0.0", "hellojs": "^1.20.0", "inspectpack": "^4.7.1", - "intro.js": "^7.0.1", + "intro.js": "^7.2.0", "jquery": "^3.7.0", "lodash": "^4.17.21", "moment": "^2.29.4", "nconf": "^0.12.0", "sass": "^1.63.4", "sass-loader": "^8.0.2", - "smartbanner.js": "^1.19.2", + "smartbanner.js": "^1.19.3", "stopword": "^2.0.8", "svg-inline-loader": "^0.8.2", "svg-url-loader": "^7.1.1", diff --git a/website/client/public/static/npc/normal/pixel_border.png b/website/client/public/static/npc/normal/pixel_border.png new file mode 100644 index 0000000000..8e873389ca Binary files /dev/null and b/website/client/public/static/npc/normal/pixel_border.png differ diff --git a/website/client/src/app.vue b/website/client/src/app.vue index 95cb2ba425..5166789ed3 100644 --- a/website/client/src/app.vue +++ b/website/client/src/app.vue @@ -41,6 +41,7 @@