Merge remote-tracking branch 'origin/develop' into negue/ui/setting
# Conflicts: # package-lock.json # website/client/src/components/settings/site.vue # website/client/src/components/tasks/column.vue # website/server/models/user/schema.js
This commit is contained in:
+1
-1
Submodule habitica-images updated: dac6a71d1f...3cda1c1a27
@@ -0,0 +1,158 @@
|
||||
/* eslint-disable no-console */
|
||||
const MIGRATION_NAME = '20230522_pet_group_achievements';
|
||||
import { model as User } from '../../../website/server/models/user';
|
||||
|
||||
const progressCount = 1000;
|
||||
let count = 0;
|
||||
|
||||
async function updateUser (user) {
|
||||
count++;
|
||||
|
||||
const set = {
|
||||
migration: MIGRATION_NAME,
|
||||
};
|
||||
|
||||
if (user && user.items && user.items.pets) {
|
||||
const pets = user.items.pets;
|
||||
if (pets['Parrot-Base']
|
||||
&& pets['Parrot-CottonCandyBlue']
|
||||
&& pets['Parrot-CottonCandyPink']
|
||||
&& pets['Parrot-Desert']
|
||||
&& pets['Parrot-Golden']
|
||||
&& pets['Parrot-Red']
|
||||
&& pets['Parrot-Shade']
|
||||
&& pets['Parrot-Skeleton']
|
||||
&& pets['Parrot-White']
|
||||
&& pets['Parrot-Zombie']
|
||||
&& pets['Rooster-Base']
|
||||
&& pets['Rooster-CottonCandyBlue']
|
||||
&& pets['Rooster-CottonCandyPink']
|
||||
&& pets['Rooster-Desert']
|
||||
&& pets['Rooster-Golden']
|
||||
&& pets['Rooster-Red']
|
||||
&& pets['Rooster-Shade']
|
||||
&& pets['Rooster-Skeleton']
|
||||
&& pets['Rooster-White']
|
||||
&& pets['Rooster-Zombie']
|
||||
&& pets['Triceratops-Base']
|
||||
&& pets['Triceratops-CottonCandyBlue']
|
||||
&& pets['Triceratops-CottonCandyPink']
|
||||
&& pets['Triceratops-Desert']
|
||||
&& pets['Triceratops-Golden']
|
||||
&& pets['Triceratops-Red']
|
||||
&& pets['Triceratops-Shade']
|
||||
&& pets['Triceratops-Skeleton']
|
||||
&& pets['Triceratops-White']
|
||||
&& pets['Triceratops-Zombie']
|
||||
&& pets['TRex-Base']
|
||||
&& pets['TRex-CottonCandyBlue']
|
||||
&& pets['TRex-CottonCandyPink']
|
||||
&& pets['TRex-Desert']
|
||||
&& pets['TRex-Golden']
|
||||
&& pets['TRex-Red']
|
||||
&& pets['TRex-Shade']
|
||||
&& pets['TRex-Skeleton']
|
||||
&& pets['TRex-White']
|
||||
&& pets['TRex-Zombie']
|
||||
&& pets['Pterodactyl-Base']
|
||||
&& pets['Pterodactyl-CottonCandyBlue']
|
||||
&& pets['Pterodactyl-CottonCandyPink']
|
||||
&& pets['Pterodactyl-Desert']
|
||||
&& pets['Pterodactyl-Golden']
|
||||
&& pets['Pterodactyl-Red']
|
||||
&& pets['Pterodactyl-Shade']
|
||||
&& pets['Pterodactyl-Skeleton']
|
||||
&& pets['Pterodactyl-White']
|
||||
&& pets['Pterodactyl-Zombie']
|
||||
&& pets['Owl-Base']
|
||||
&& pets['Owl-CottonCandyBlue']
|
||||
&& pets['Owl-CottonCandyPink']
|
||||
&& pets['Owl-Desert']
|
||||
&& pets['Owl-Golden']
|
||||
&& pets['Owl-Red']
|
||||
&& pets['Owl-Shade']
|
||||
&& pets['Owl-Skeleton']
|
||||
&& pets['Owl-White']
|
||||
&& pets['Owl-Zombie']
|
||||
&& pets['Velociraptor-Base']
|
||||
&& pets['Velociraptor-CottonCandyBlue']
|
||||
&& pets['Velociraptor-CottonCandyPink']
|
||||
&& pets['Velociraptor-Desert']
|
||||
&& pets['Velociraptor-Golden']
|
||||
&& pets['Velociraptor-Red']
|
||||
&& pets['Velociraptor-Shade']
|
||||
&& pets['Velociraptor-Skeleton']
|
||||
&& pets['Velociraptor-White']
|
||||
&& pets['Velociraptor-Zombie']
|
||||
&& pets['Penguin-Base']
|
||||
&& pets['Penguin-CottonCandyBlue']
|
||||
&& pets['Penguin-CottonCandyPink']
|
||||
&& pets['Penguin-Desert']
|
||||
&& pets['Penguin-Golden']
|
||||
&& pets['Penguin-Red']
|
||||
&& pets['Penguin-Shade']
|
||||
&& pets['Penguin-Skeleton']
|
||||
&& pets['Penguin-White']
|
||||
&& pets['Penguin-Zombie']
|
||||
&& pets['Falcon-Base']
|
||||
&& pets['Falcon-CottonCandyBlue']
|
||||
&& pets['Falcon-CottonCandyPink']
|
||||
&& pets['Falcon-Desert']
|
||||
&& pets['Falcon-Golden']
|
||||
&& pets['Falcon-Red']
|
||||
&& pets['Falcon-Shade']
|
||||
&& pets['Falcon-Skeleton']
|
||||
&& pets['Falcon-White']
|
||||
&& pets['Falcon-Zombie']
|
||||
&& pets['Peacock-Base']
|
||||
&& pets['Peacock-CottonCandyBlue']
|
||||
&& pets['Peacock-CottonCandyPink']
|
||||
&& pets['Peacock-Desert']
|
||||
&& pets['Peacock-Golden']
|
||||
&& pets['Peacock-Red']
|
||||
&& pets['Peacock-Shade']
|
||||
&& pets['Peacock-Skeleton']
|
||||
&& pets['Peacock-White']
|
||||
&& pets['Peacock-Zombie']) {
|
||||
set['achievements.dinosaurDynasty'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
|
||||
|
||||
return await User.update({ _id: user._id }, { $set: set }).exec();
|
||||
}
|
||||
|
||||
export default async function processUsers () {
|
||||
let query = {
|
||||
// migration: { $ne: MIGRATION_NAME },
|
||||
'auth.timestamps.loggedin': { $gt: new Date('2023-04-15') },
|
||||
};
|
||||
|
||||
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)
|
||||
.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]._id,
|
||||
};
|
||||
}
|
||||
|
||||
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
|
||||
}
|
||||
};
|
||||
Generated
+593
-523
File diff suppressed because it is too large
Load Diff
+7
-7
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "habitica",
|
||||
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
|
||||
"version": "4.270.0",
|
||||
"version": "4.271.0",
|
||||
"main": "./website/server/index.js",
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.21.4",
|
||||
"@babel/preset-env": "^7.20.2",
|
||||
"@babel/core": "^7.21.8",
|
||||
"@babel/preset-env": "^7.21.5",
|
||||
"@babel/register": "^7.21.0",
|
||||
"@google-cloud/trace-agent": "^7.1.2",
|
||||
"@parse/node-apn": "^5.1.3",
|
||||
@@ -43,7 +43,7 @@
|
||||
"image-size": "^1.0.2",
|
||||
"in-app-purchase": "^1.11.3",
|
||||
"js2xmlparser": "^5.0.0",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"jsonwebtoken": "^9.0.0",
|
||||
"jwks-rsa": "^2.1.5",
|
||||
"lodash": "^4.17.21",
|
||||
"merge-stream": "^2.0.0",
|
||||
@@ -68,8 +68,8 @@
|
||||
"remove-markdown": "^0.5.0",
|
||||
"rimraf": "^3.0.2",
|
||||
"short-uuid": "^4.2.2",
|
||||
"stripe": "^11.10.0",
|
||||
"superagent": "^8.0.6",
|
||||
"stripe": "^12.6.0",
|
||||
"superagent": "^8.0.9",
|
||||
"universal-analytics": "^0.5.3",
|
||||
"useragent": "^2.1.9",
|
||||
"uuid": "^9.0.0",
|
||||
@@ -123,7 +123,7 @@
|
||||
"monk": "^7.3.4",
|
||||
"require-again": "^2.0.0",
|
||||
"run-rs": "^0.7.7",
|
||||
"sinon": "^15.0.4",
|
||||
"sinon": "^15.1.0",
|
||||
"sinon-chai": "^3.7.0",
|
||||
"sinon-stub-promise": "^4.0.0"
|
||||
},
|
||||
|
||||
@@ -748,9 +748,19 @@ describe('payments/index', () => {
|
||||
});
|
||||
|
||||
it('does not add to plans.consecutive.offset if 1 month subscription', async () => {
|
||||
data.sub.key = 'basic_earned';
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.extraMonths).to.eql(0);
|
||||
expect(user.purchased.plan.consecutive.offset).to.eql(0);
|
||||
});
|
||||
|
||||
it('resets plans.consecutive.offset if 1 month subscription', async () => {
|
||||
user.purchased.plan.consecutive.offset = 1;
|
||||
await user.save();
|
||||
data.sub.key = 'basic_earned';
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.consecutive.offset).to.eql(0);
|
||||
});
|
||||
|
||||
it('adds 5 to plan.consecutive.gemCapExtra for 3 month block', async () => {
|
||||
|
||||
@@ -242,7 +242,7 @@ describe('cron middleware', () => {
|
||||
|
||||
sandbox.spy(cronLib, 'recoverCron');
|
||||
|
||||
sandbox.stub(User, 'update')
|
||||
sandbox.stub(User, 'updateOne')
|
||||
.withArgs({
|
||||
_id: user._id,
|
||||
$or: [
|
||||
|
||||
@@ -1732,7 +1732,7 @@ describe('Group Model', () => {
|
||||
});
|
||||
|
||||
it('updates participting members (not including user)', async () => {
|
||||
sandbox.spy(User, 'update');
|
||||
sandbox.spy(User, 'updateMany');
|
||||
|
||||
await party.startQuest(nonParticipatingMember);
|
||||
|
||||
@@ -1740,7 +1740,7 @@ describe('Group Model', () => {
|
||||
questLeader._id, participatingMember._id, sleepingParticipatingMember._id,
|
||||
];
|
||||
|
||||
expect(User.update).to.be.calledWith(
|
||||
expect(User.updateMany).to.be.calledWith(
|
||||
{ _id: { $in: members } },
|
||||
{
|
||||
$set: {
|
||||
@@ -1753,11 +1753,11 @@ describe('Group Model', () => {
|
||||
});
|
||||
|
||||
it('updates non-user quest leader and decrements quest scroll', async () => {
|
||||
sandbox.spy(User, 'update');
|
||||
sandbox.spy(User, 'updateOne');
|
||||
|
||||
await party.startQuest(participatingMember);
|
||||
|
||||
expect(User.update).to.be.calledWith(
|
||||
expect(User.updateOne).to.be.calledWith(
|
||||
{ _id: questLeader._id },
|
||||
{
|
||||
$inc: {
|
||||
@@ -1819,29 +1819,29 @@ describe('Group Model', () => {
|
||||
};
|
||||
|
||||
it('doesn\'t retry successful operations', async () => {
|
||||
sandbox.stub(User, 'update').returns(successfulMock);
|
||||
sandbox.stub(User, 'updateOne').returns(successfulMock);
|
||||
|
||||
await party.finishQuest(quest);
|
||||
|
||||
expect(User.update).to.be.calledThrice;
|
||||
expect(User.updateOne).to.be.calledThrice;
|
||||
});
|
||||
|
||||
it('stops retrying when a successful update has occurred', async () => {
|
||||
const updateStub = sandbox.stub(User, 'update');
|
||||
const updateStub = sandbox.stub(User, 'updateOne');
|
||||
updateStub.onCall(0).returns(failedMock);
|
||||
updateStub.returns(successfulMock);
|
||||
|
||||
await party.finishQuest(quest);
|
||||
|
||||
expect(User.update.callCount).to.equal(4);
|
||||
expect(User.updateOne.callCount).to.equal(4);
|
||||
});
|
||||
|
||||
it('retries failed updates at most five times per user', async () => {
|
||||
sandbox.stub(User, 'update').returns(failedMock);
|
||||
sandbox.stub(User, 'updateOne').returns(failedMock);
|
||||
|
||||
await expect(party.finishQuest(quest)).to.eventually.be.rejected;
|
||||
|
||||
expect(User.update.callCount).to.eql(15); // for 3 users
|
||||
expect(User.updateOne.callCount).to.eql(15); // for 3 users
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2088,17 +2088,17 @@ describe('Group Model', () => {
|
||||
|
||||
context('Party quests', () => {
|
||||
it('updates participating members with rewards', async () => {
|
||||
sandbox.spy(User, 'update');
|
||||
sandbox.spy(User, 'updateOne');
|
||||
await party.finishQuest(quest);
|
||||
|
||||
expect(User.update).to.be.calledThrice;
|
||||
expect(User.update).to.be.calledWithMatch({
|
||||
expect(User.updateOne).to.be.calledThrice;
|
||||
expect(User.updateOne).to.be.calledWithMatch({
|
||||
_id: questLeader._id,
|
||||
});
|
||||
expect(User.update).to.be.calledWithMatch({
|
||||
expect(User.updateOne).to.be.calledWithMatch({
|
||||
_id: participatingMember._id,
|
||||
});
|
||||
expect(User.update).to.be.calledWithMatch({
|
||||
expect(User.updateOne).to.be.calledWithMatch({
|
||||
_id: sleepingParticipatingMember._id,
|
||||
});
|
||||
});
|
||||
@@ -2173,11 +2173,11 @@ describe('Group Model', () => {
|
||||
});
|
||||
|
||||
it('updates all users with rewards', async () => {
|
||||
sandbox.spy(User, 'update');
|
||||
sandbox.spy(User, 'updateMany');
|
||||
await party.finishQuest(tavernQuest);
|
||||
|
||||
expect(User.update).to.be.calledOnce;
|
||||
expect(User.update).to.be.calledWithMatch({});
|
||||
expect(User.updateMany).to.be.calledOnce;
|
||||
expect(User.updateMany).to.be.calledWithMatch({});
|
||||
});
|
||||
|
||||
it('sets quest completed to the world quest key', async () => {
|
||||
|
||||
@@ -202,18 +202,86 @@ describe('POST /user/class/cast/:spellId', () => {
|
||||
await group.groupLeader.post('/user/class/cast/mpheal');
|
||||
|
||||
promises = [];
|
||||
promises.push(group.groupLeader.sync());
|
||||
promises.push(group.members[0].sync());
|
||||
promises.push(group.members[1].sync());
|
||||
promises.push(group.members[2].sync());
|
||||
promises.push(group.members[3].sync());
|
||||
await Promise.all(promises);
|
||||
|
||||
expect(group.groupLeader.stats.mp).to.be.equal(170); // spell caster
|
||||
expect(group.members[0].stats.mp).to.be.greaterThan(0); // warrior
|
||||
expect(group.members[1].stats.mp).to.equal(0); // wizard
|
||||
expect(group.members[2].stats.mp).to.be.greaterThan(0); // rogue
|
||||
expect(group.members[3].stats.mp).to.be.greaterThan(0); // healer
|
||||
});
|
||||
|
||||
const spellList = [
|
||||
{
|
||||
className: 'warrior',
|
||||
spells: [['smash', 'task'], ['defensiveStance'], ['valorousPresence'], ['intimidate']],
|
||||
},
|
||||
{
|
||||
className: 'wizard',
|
||||
spells: [['fireball', 'task'], ['mpheal'], ['earth'], ['frost']],
|
||||
},
|
||||
{
|
||||
className: 'healer',
|
||||
spells: [['heal'], ['brightness'], ['protectAura'], ['healAll']],
|
||||
},
|
||||
{
|
||||
className: 'rogue',
|
||||
spells: [['pickPocket', 'task'], ['backStab', 'task'], ['toolsOfTrade'], ['stealth']],
|
||||
},
|
||||
];
|
||||
|
||||
spellList.forEach(async habitClass => {
|
||||
describe(`For a ${habitClass.className}`, async () => {
|
||||
habitClass.spells.forEach(async spell => {
|
||||
describe(`Using ${spell[0]}`, async () => {
|
||||
it('Deducts MP from spell caster', async () => {
|
||||
const { groupLeader } = await createAndPopulateGroup({
|
||||
groupDetails: { type: 'party', privacy: 'private' },
|
||||
members: 3,
|
||||
});
|
||||
await groupLeader.update({
|
||||
'stats.mp': 200, 'stats.class': habitClass.className, 'stats.lvl': 20, 'stats.hp': 40,
|
||||
});
|
||||
// need this for task spells and for stealth
|
||||
const task = await groupLeader.post('/tasks/user', {
|
||||
text: 'test habit',
|
||||
type: 'daily',
|
||||
});
|
||||
if (spell.length === 2 && spell[1] === 'task') {
|
||||
await groupLeader.post(`/user/class/cast/${spell[0]}?targetId=${task._id}`);
|
||||
} else {
|
||||
await groupLeader.post(`/user/class/cast/${spell[0]}`);
|
||||
}
|
||||
await groupLeader.sync();
|
||||
expect(groupLeader.stats.mp).to.be.lessThan(200);
|
||||
});
|
||||
it('works without a party', async () => {
|
||||
await user.update({
|
||||
'stats.mp': 200, 'stats.class': habitClass.className, 'stats.lvl': 20, 'stats.hp': 40,
|
||||
});
|
||||
// need this for task spells and for stealth
|
||||
const task = await user.post('/tasks/user', {
|
||||
text: 'test habit',
|
||||
type: 'daily',
|
||||
});
|
||||
if (spell.length === 2 && spell[1] === 'task') {
|
||||
await user.post(`/user/class/cast/${spell[0]}?targetId=${task._id}`);
|
||||
} else {
|
||||
await user.post(`/user/class/cast/${spell[0]}`);
|
||||
}
|
||||
await user.sync();
|
||||
expect(user.stats.mp).to.be.lessThan(200);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('cast bulk', async () => {
|
||||
let { group, groupLeader } = await createAndPopulateGroup({ // eslint-disable-line prefer-const
|
||||
groupDetails: { type: 'party', privacy: 'private' },
|
||||
|
||||
Generated
+39
-32
@@ -16921,9 +16921,9 @@
|
||||
}
|
||||
},
|
||||
"core-js": {
|
||||
"version": "3.30.1",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.30.1.tgz",
|
||||
"integrity": "sha512-ZNS5nbiSwDTq4hFosEDqm65izl2CWmLz0hARJMyNQBgkUZMIF51cQiMvIQKA6hvuaeWxQDP3hEedM1JZIgTldQ=="
|
||||
"version": "3.30.2",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.30.2.tgz",
|
||||
"integrity": "sha512-uBJiDmwqsbJCWHAwjrx3cvjbMXP7xD72Dmsn5LOJpiRmE3WbBbN5rCqQ2Qh6Ek6/eOrjlWngEynBWo4VxerQhg=="
|
||||
},
|
||||
"core-js-compat": {
|
||||
"version": "3.11.0",
|
||||
@@ -17972,9 +17972,9 @@
|
||||
}
|
||||
},
|
||||
"dompurify": {
|
||||
"version": "2.4.3",
|
||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.3.tgz",
|
||||
"integrity": "sha512-q6QaLcakcRjebxjg8/+NP+h0rPfatOgOzc46Fst9VAA3jF2ApfKBNKMzdP4DYTqtUMXSCd5pRS/8Po/OmoCHZQ=="
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.0.3.tgz",
|
||||
"integrity": "sha512-axQ9zieHLnAnHh0sfAamKYiqXMJAVwu+LM/alQ7WDagoWessyWvMSFyW65CqF3owufNu8HBcE4cM2Vflu7YWcQ=="
|
||||
},
|
||||
"domutils": {
|
||||
"version": "1.7.0",
|
||||
@@ -21107,6 +21107,11 @@
|
||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz",
|
||||
"integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg=="
|
||||
},
|
||||
"immutable": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.0.tgz",
|
||||
"integrity": "sha512-0AOCmOip+xgJwEVTQj1EfiDDOkPmuyllDuTuEX+DDXUgapLAsBIfkg3sxCYyCEA8mQqZrrxPUGjcOQ2JS3WLkg=="
|
||||
},
|
||||
"import-cwd": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-2.1.0.tgz",
|
||||
@@ -21467,9 +21472,9 @@
|
||||
"integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA=="
|
||||
},
|
||||
"intro.js": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/intro.js/-/intro.js-6.0.0.tgz",
|
||||
"integrity": "sha512-ZUiR6BoLSvPSlLG0boewnWVgji1fE1gBvP/pyw5pgCKXEDQz1mMeUxarggClPNs71UTq364LwSk9zxz17A9gaQ=="
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/intro.js/-/intro.js-7.0.1.tgz",
|
||||
"integrity": "sha512-1oqz6aOz9cGQ3CrtVYhCSo6AkjnXUn302kcIWLaZ3TI4kKssRXDwDSz4VRoGcfC1jN+WfaSJXRBrITz+QVEBzg=="
|
||||
},
|
||||
"invariant": {
|
||||
"version": "2.2.4",
|
||||
@@ -22049,9 +22054,9 @@
|
||||
}
|
||||
},
|
||||
"jquery": {
|
||||
"version": "3.6.4",
|
||||
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.4.tgz",
|
||||
"integrity": "sha512-v28EW9DWDFpzcD9O5iyJXg3R3+q+mET5JhnjJzQUZMHOv67bpSIHq81GEYpPNZHG+XXHsfSme3nxp/hndKEcsQ=="
|
||||
"version": "3.7.0",
|
||||
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.0.tgz",
|
||||
"integrity": "sha512-umpJ0/k8X0MvD1ds0P9SfowREz2LenHsQaxSohMZ5OMNEU2r0tf8pdeEFTHMFxWVxKNyU9rTtK3CWzUCTKJUeQ=="
|
||||
},
|
||||
"js-message": {
|
||||
"version": "1.0.5",
|
||||
@@ -27397,17 +27402,19 @@
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
||||
},
|
||||
"sass": {
|
||||
"version": "1.34.0",
|
||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.34.0.tgz",
|
||||
"integrity": "sha512-rHEN0BscqjUYuomUEaqq3BMgsXqQfkcMVR7UhscsAVub0/spUrZGBMxQXFS2kfiDsPLZw5yuU9iJEFNC2x38Qw==",
|
||||
"version": "1.62.1",
|
||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.62.1.tgz",
|
||||
"integrity": "sha512-NHpxIzN29MXvWiuswfc1W3I0N8SXBd8UR26WntmDlRYf0bSADnwnOjsyMZ3lMezSlArD33Vs3YFhp7dWvL770A==",
|
||||
"requires": {
|
||||
"chokidar": ">=3.0.0 <4.0.0"
|
||||
"chokidar": ">=3.0.0 <4.0.0",
|
||||
"immutable": "^4.0.0",
|
||||
"source-map-js": ">=0.6.2 <2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"anymatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz",
|
||||
"integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==",
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
|
||||
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
|
||||
"requires": {
|
||||
"normalize-path": "^3.0.0",
|
||||
"picomatch": "^2.0.4"
|
||||
@@ -27427,18 +27434,18 @@
|
||||
}
|
||||
},
|
||||
"chokidar": {
|
||||
"version": "3.5.1",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz",
|
||||
"integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==",
|
||||
"version": "3.5.3",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
|
||||
"integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
|
||||
"requires": {
|
||||
"anymatch": "~3.1.1",
|
||||
"anymatch": "~3.1.2",
|
||||
"braces": "~3.0.2",
|
||||
"fsevents": "~2.3.1",
|
||||
"glob-parent": "~5.1.0",
|
||||
"fsevents": "~2.3.2",
|
||||
"glob-parent": "~5.1.2",
|
||||
"is-binary-path": "~2.1.0",
|
||||
"is-glob": "~4.0.1",
|
||||
"normalize-path": "~3.0.0",
|
||||
"readdirp": "~3.5.0"
|
||||
"readdirp": "~3.6.0"
|
||||
}
|
||||
},
|
||||
"fill-range": {
|
||||
@@ -27477,9 +27484,9 @@
|
||||
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="
|
||||
},
|
||||
"readdirp": {
|
||||
"version": "3.5.0",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz",
|
||||
"integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==",
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
|
||||
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
|
||||
"requires": {
|
||||
"picomatch": "^2.2.1"
|
||||
}
|
||||
@@ -30301,9 +30308,9 @@
|
||||
"integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
|
||||
},
|
||||
"uuid": {
|
||||
"version": "8.3.2",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
||||
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
|
||||
"version": "9.0.0",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz",
|
||||
"integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg=="
|
||||
},
|
||||
"uuid-browser": {
|
||||
"version": "3.1.0",
|
||||
|
||||
@@ -32,8 +32,8 @@
|
||||
"bootstrap": "^4.6.0",
|
||||
"bootstrap-vue": "^2.23.1",
|
||||
"chai": "^4.3.7",
|
||||
"core-js": "^3.30.1",
|
||||
"dompurify": "^2.4.3",
|
||||
"core-js": "^3.30.2",
|
||||
"dompurify": "^3.0.3",
|
||||
"eslint": "^6.8.0",
|
||||
"eslint-config-habitrpg": "^6.2.0",
|
||||
"eslint-plugin-mocha": "^5.3.0",
|
||||
@@ -41,12 +41,12 @@
|
||||
"habitica-markdown": "^3.0.0",
|
||||
"hellojs": "^1.20.0",
|
||||
"inspectpack": "^4.7.1",
|
||||
"intro.js": "^6.0.0",
|
||||
"jquery": "^3.6.4",
|
||||
"intro.js": "^7.0.1",
|
||||
"jquery": "^3.7.0",
|
||||
"lodash": "^4.17.21",
|
||||
"moment": "^2.29.4",
|
||||
"nconf": "^0.12.0",
|
||||
"sass": "^1.34.0",
|
||||
"sass": "^1.62.1",
|
||||
"sass-loader": "^8.0.2",
|
||||
"smartbanner.js": "^1.19.2",
|
||||
"stopword": "^2.0.8",
|
||||
@@ -54,7 +54,7 @@
|
||||
"svg-url-loader": "^7.1.1",
|
||||
"svgo": "^1.3.2",
|
||||
"svgo-loader": "^2.2.1",
|
||||
"uuid": "^8.3.2",
|
||||
"uuid": "^9.0.0",
|
||||
"validator": "^13.9.0",
|
||||
"vue": "^2.7.10",
|
||||
"vue-cli-plugin-storybook": "2.1.0",
|
||||
|
||||
@@ -138,6 +138,11 @@
|
||||
width: 48px;
|
||||
height: 52px;
|
||||
}
|
||||
.achievement-dinosaurDynasty2x {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/achievement-dinosaurDynasty2x.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.achievement-domesticated2x {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/achievement-domesticated2x.png');
|
||||
width: 60px;
|
||||
|
||||
@@ -17,10 +17,18 @@
|
||||
Payment schedule ("basic-earned" is monthly):
|
||||
<strong>{{ hero.purchased.plan.planId }}</strong>
|
||||
</div>
|
||||
<div v-if="hero.purchased.plan.planId == 'group_plan_auto'">
|
||||
Group plan ID:
|
||||
<strong>{{ hero.purchased.plan.owner }}</strong>
|
||||
</div>
|
||||
<div v-if="hero.purchased.plan.dateCreated">
|
||||
Creation date:
|
||||
<strong>{{ dateFormat(hero.purchased.plan.dateCreated) }}</strong>
|
||||
</div>
|
||||
<div v-if="hero.purchased.plan.dateCurrentTypeCreated">
|
||||
Start date for current subscription type:
|
||||
<strong>{{ dateFormat(hero.purchased.plan.dateCurrentTypeCreated) }}</strong>
|
||||
</div>
|
||||
<div>
|
||||
Termination date:
|
||||
<strong
|
||||
@@ -46,9 +54,16 @@
|
||||
Perk offset months:
|
||||
<strong>{{ hero.purchased.plan.consecutive.offset }}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<div class="form-inline">
|
||||
Perk month count:
|
||||
<strong>{{ hero.purchased.plan.perkMonthCount }}</strong>
|
||||
<input
|
||||
v-model="hero.purchased.plan.perkMonthCount"
|
||||
class="form-control"
|
||||
type="number"
|
||||
min="0"
|
||||
max="2"
|
||||
step="1"
|
||||
>
|
||||
</div>
|
||||
<div>
|
||||
Next Mystic Hourglass:
|
||||
|
||||
@@ -521,7 +521,12 @@ export default {
|
||||
// Get Category Filter Labels
|
||||
this.typeFilters = getFilterLabels(this.type, this.challenge);
|
||||
// Set default filter for task column
|
||||
this.activateFilter(this.type);
|
||||
|
||||
if (this.challenge) {
|
||||
this.activateFilter(this.type);
|
||||
} else {
|
||||
this.activateFilter(this.type, this.user.preferences.tasks.activeFilter[this.type], true);
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.setColumnBackgroundVisibility();
|
||||
@@ -661,7 +666,7 @@ export default {
|
||||
taskSummary (task) {
|
||||
this.$emit('taskSummary', task);
|
||||
},
|
||||
activateFilter (type, filter = '') {
|
||||
activateFilter (type, filter = '', skipSave = false) {
|
||||
// Needs a separate API call as this data may not reside in store
|
||||
if (type === 'todo' && filter === 'complete2') {
|
||||
if (this.group && this.group._id) {
|
||||
@@ -684,6 +689,11 @@ export default {
|
||||
}
|
||||
|
||||
this.activeFilter = getActiveFilter(type, filter, this.challenge);
|
||||
|
||||
if (!skipSave && !this.challenge) {
|
||||
const propertyToUpdate = `preferences.tasks.activeFilter.${type}`;
|
||||
this.$store.dispatch('user:set', { [propertyToUpdate]: filter });
|
||||
}
|
||||
},
|
||||
setColumnBackgroundVisibility () {
|
||||
this.$nextTick(() => {
|
||||
|
||||
@@ -114,7 +114,7 @@ export default {
|
||||
this.castCancel();
|
||||
|
||||
// the selected member doesn't have the flags property which sets `cardReceived`
|
||||
if (spell.pinType !== 'card') {
|
||||
if (spell.pinType !== 'card' && spell.bulk !== true) {
|
||||
try {
|
||||
spell.cast(this.user, target, {});
|
||||
} catch (e) {
|
||||
|
||||
@@ -21,6 +21,18 @@ describe('Task Column', () => {
|
||||
getters: {
|
||||
'tasks:getFilteredTaskList': () => [],
|
||||
},
|
||||
|
||||
state: {
|
||||
user: {
|
||||
data: {
|
||||
preferences: {
|
||||
tasks: {
|
||||
activeFilter: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
mocks,
|
||||
stubs,
|
||||
@@ -76,7 +88,20 @@ describe('Task Column', () => {
|
||||
'tasks:getFilteredTaskList': () => () => habits,
|
||||
};
|
||||
|
||||
const store = new Store({ getters });
|
||||
const store = new Store({
|
||||
getters,
|
||||
state: {
|
||||
user: {
|
||||
data: {
|
||||
preferences: {
|
||||
tasks: {
|
||||
activeFilter: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
wrapper = makeWrapper({ store });
|
||||
});
|
||||
|
||||
@@ -22,7 +22,13 @@ describe('Task', () => {
|
||||
state: {
|
||||
user: {
|
||||
data: {
|
||||
preferences: {},
|
||||
preferences: {
|
||||
tasks: {
|
||||
activeFilter: {
|
||||
|
||||
},
|
||||
},
|
||||
},
|
||||
...additionalUserData,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -147,5 +147,8 @@
|
||||
"achievementPolarProModalText": "You collected all the Polar Pets!",
|
||||
"achievementPlantParent": "Plant Parent",
|
||||
"achievementPlantParentText": "Has hatched all standard colors of Plant pets: Cactus and Treeling!",
|
||||
"achievementPlantParentModalText": "You collected all the Plant Pets!"
|
||||
"achievementPlantParentModalText": "You collected all the Plant Pets!",
|
||||
"achievementDinosaurDynasty": "Dinosaur Dynasty",
|
||||
"achievementDinosaurDynastyText": "Has hatched all standard colors of bird and dinosaur pets: Falcon, Owl, Parrot, Peacock, Penguin, Rooster, Pterodactyl, T-Rex, Triceratops, and Velociraptor!",
|
||||
"achievementDinosaurDynastyModalText": "You collected all the bird and dinosaur pets!"
|
||||
}
|
||||
|
||||
@@ -178,6 +178,11 @@ const animalSetAchievs = {
|
||||
titleKey: 'achievementBirdsOfAFeather',
|
||||
textKey: 'achievementBirdsOfAFeatherText',
|
||||
},
|
||||
dinosaurDynasty: {
|
||||
icon: 'achievement-dinosaurDynasty',
|
||||
titleKey: 'achievementDinosaurDynasty',
|
||||
textKey: 'achievementDinosaurDynastyText',
|
||||
},
|
||||
domesticated: {
|
||||
icon: 'achievement-domesticated',
|
||||
titleKey: 'achievementDomesticated',
|
||||
|
||||
@@ -178,9 +178,9 @@ const bundles = {
|
||||
'penguin',
|
||||
'rooster',
|
||||
],
|
||||
event: EVENTS.bundle202109,
|
||||
event: EVENTS.bundle202305,
|
||||
canBuy () {
|
||||
return moment().isBefore(EVENTS.bundle202109.end);
|
||||
return moment().isBefore(EVENTS.bundle202305.end);
|
||||
},
|
||||
type: 'quests',
|
||||
value: 7,
|
||||
|
||||
@@ -26,6 +26,23 @@ const ANIMAL_SET_ACHIEVEMENTS = {
|
||||
achievementKey: 'birdsOfAFeather',
|
||||
notificationType: 'ACHIEVEMENT_ANIMAL_SET',
|
||||
},
|
||||
dinosaurDynasty: {
|
||||
type: 'pet',
|
||||
species: [
|
||||
'Falcon',
|
||||
'Owl',
|
||||
'Parrot',
|
||||
'Peacock',
|
||||
'Penguin',
|
||||
'Rooster',
|
||||
'Pterodactyl',
|
||||
'TRex',
|
||||
'Triceratops',
|
||||
'Velociraptor',
|
||||
],
|
||||
achievementKey: 'dinosaurDynasty',
|
||||
notificationType: 'ACHIEVEMENT_ANIMAL_SET',
|
||||
},
|
||||
domesticated: {
|
||||
type: 'pet',
|
||||
species: [
|
||||
|
||||
@@ -10,11 +10,19 @@ const gemsPromo = {
|
||||
|
||||
export const EVENTS = {
|
||||
noEvent: {
|
||||
start: '2023-05-01T23:59-04:00',
|
||||
start: '2023-05-31T23:59-04:00',
|
||||
end: '2023-06-22T08:00-04:00',
|
||||
season: 'normal',
|
||||
npcImageSuffix: '',
|
||||
},
|
||||
bundle202305: {
|
||||
start:'2023-05-23T08:00-04:00',
|
||||
end:'2023-05-31T23:59-04:00',
|
||||
},
|
||||
potions202305: {
|
||||
start:'2023-05-16T08:00-04:00',
|
||||
end:'2023-05-31T23:59-04:00',
|
||||
},
|
||||
aprilFools2023: {
|
||||
start: '2023-04-01T08:00-04:00',
|
||||
end: '2023-04-02T08:00-04:00',
|
||||
|
||||
@@ -88,26 +88,26 @@ const premium = {
|
||||
value: 2,
|
||||
text: t('hatchingPotionFairy'),
|
||||
limited: true,
|
||||
event: EVENTS.potions202105,
|
||||
event: EVENTS.potions202305,
|
||||
_addlNotes: t('eventAvailabilityReturning', {
|
||||
availableDate: t('dateEndMay'),
|
||||
previousDate: t('mayYYYY', { year: 2020 }),
|
||||
previousDate: t('mayYYYY', { year: 2021 }),
|
||||
}),
|
||||
canBuy () {
|
||||
return moment().isBefore(EVENTS.potions202105.end);
|
||||
return moment().isBefore(EVENTS.potions202305.end);
|
||||
},
|
||||
},
|
||||
Floral: {
|
||||
value: 2,
|
||||
text: t('hatchingPotionFloral'),
|
||||
limited: true,
|
||||
event: EVENTS.potions202205,
|
||||
event: EVENTS.potions202305,
|
||||
_addlNotes: t('eventAvailabilityReturning', {
|
||||
availableDate: t('dateEndMay'),
|
||||
previousDate: t('mayYYYY', { year: 2021 }),
|
||||
previousDate: t('mayYYYY', { year: 2022 }),
|
||||
}),
|
||||
canBuy () {
|
||||
return moment().isBefore(EVENTS.potions202205.end);
|
||||
return moment().isBefore(EVENTS.potions202305.end);
|
||||
},
|
||||
},
|
||||
Aquatic: {
|
||||
|
||||
@@ -5,7 +5,7 @@ import { EVENTS } from './constants';
|
||||
// path: 'premiumHatchingPotions.Rainbow',
|
||||
const featuredItems = {
|
||||
market () {
|
||||
if (moment().isBetween(EVENTS.spring2023.start, EVENTS.spring2023.end)) {
|
||||
if (moment().isBetween(EVENTS.potions202305.start, EVENTS.potions202305.end)) {
|
||||
return [
|
||||
{
|
||||
type: 'armoire',
|
||||
@@ -13,15 +13,15 @@ const featuredItems = {
|
||||
},
|
||||
{
|
||||
type: 'premiumHatchingPotion',
|
||||
path: 'premiumHatchingPotions.PolkaDot',
|
||||
path: 'premiumHatchingPotions.Fairy',
|
||||
},
|
||||
{
|
||||
type: 'premiumHatchingPotion',
|
||||
path: 'premiumHatchingPotions.BirchBark',
|
||||
path: 'premiumHatchingPotions.Floral',
|
||||
},
|
||||
{
|
||||
type: 'premiumHatchingPotion',
|
||||
path: 'premiumHatchingPotions.Rainbow',
|
||||
type: 'hatchingPotions',
|
||||
path: 'hatchingPotions.Golden',
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -45,19 +45,19 @@ const featuredItems = {
|
||||
];
|
||||
},
|
||||
quests () { // start date is 3/28
|
||||
if (moment().isBetween(EVENTS.bundle202303.start, EVENTS.bundle202303.end)) {
|
||||
if (moment().isBetween(EVENTS.bundle202305.start, EVENTS.bundle202305.end)) {
|
||||
return [
|
||||
{
|
||||
type: 'bundles',
|
||||
path: 'bundles.jungleBuddies',
|
||||
path: 'bundles.birdBuddies',
|
||||
},
|
||||
{
|
||||
type: 'quests',
|
||||
path: 'quests.kraken',
|
||||
path: 'quests.harpy',
|
||||
},
|
||||
{
|
||||
type: 'quests',
|
||||
path: 'quests.nudibranch',
|
||||
path: 'quests.owl',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@@ -77,13 +77,11 @@ spells.wizard = {
|
||||
lvl: 12,
|
||||
target: 'party',
|
||||
notes: t('spellWizardMPHealNotes'),
|
||||
cast (user, target) {
|
||||
each(target, member => {
|
||||
const bonus = statsComputed(user).int;
|
||||
if (user._id !== member._id && member.stats.class !== 'wizard') {
|
||||
member.stats.mp += Math.ceil(diminishingReturns(bonus, 25, 125));
|
||||
}
|
||||
});
|
||||
bulk: true,
|
||||
cast (user, data) {
|
||||
const bonus = statsComputed(user).int;
|
||||
data.query['stats.class'] = { $ne: 'wizard' };
|
||||
data.update = { $inc: { 'stats.mp': Math.ceil(diminishingReturns(bonus, 25, 125)) } };
|
||||
},
|
||||
},
|
||||
earth: { // Earthquake
|
||||
@@ -92,12 +90,10 @@ spells.wizard = {
|
||||
lvl: 13,
|
||||
target: 'party',
|
||||
notes: t('spellWizardEarthNotes'),
|
||||
cast (user, target) {
|
||||
each(target, member => {
|
||||
const bonus = statsComputed(user).int - user.stats.buffs.int;
|
||||
if (!member.stats.buffs.int) member.stats.buffs.int = 0;
|
||||
member.stats.buffs.int += Math.ceil(diminishingReturns(bonus, 30, 200));
|
||||
});
|
||||
bulk: true,
|
||||
cast (user, data) {
|
||||
const bonus = statsComputed(user).int - user.stats.buffs.int;
|
||||
data.update = { $inc: { 'stats.buffs.int': Math.ceil(diminishingReturns(bonus, 30, 200)) } };
|
||||
},
|
||||
},
|
||||
frost: { // Chilling Frost
|
||||
@@ -147,12 +143,10 @@ spells.warrior = {
|
||||
lvl: 13,
|
||||
target: 'party',
|
||||
notes: t('spellWarriorValorousPresenceNotes'),
|
||||
cast (user, target) {
|
||||
each(target, member => {
|
||||
const bonus = statsComputed(user).str - user.stats.buffs.str;
|
||||
if (!member.stats.buffs.str) member.stats.buffs.str = 0;
|
||||
member.stats.buffs.str += Math.ceil(diminishingReturns(bonus, 20, 200));
|
||||
});
|
||||
bulk: true,
|
||||
cast (user, data) {
|
||||
const bonus = statsComputed(user).str - user.stats.buffs.str;
|
||||
data.update = { $inc: { 'stats.buffs.str': Math.ceil(diminishingReturns(bonus, 20, 200)) } };
|
||||
},
|
||||
},
|
||||
intimidate: { // Intimidating Gaze
|
||||
@@ -161,12 +155,10 @@ spells.warrior = {
|
||||
lvl: 14,
|
||||
target: 'party',
|
||||
notes: t('spellWarriorIntimidateNotes'),
|
||||
cast (user, target) {
|
||||
each(target, member => {
|
||||
const bonus = statsComputed(user).con - user.stats.buffs.con;
|
||||
if (!member.stats.buffs.con) member.stats.buffs.con = 0;
|
||||
member.stats.buffs.con += Math.ceil(diminishingReturns(bonus, 24, 200));
|
||||
});
|
||||
bulk: true,
|
||||
cast (user, data) {
|
||||
const bonus = statsComputed(user).con - user.stats.buffs.con;
|
||||
data.update = { $inc: { 'stats.buffs.con': Math.ceil(diminishingReturns(bonus, 24, 200)) } };
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -203,12 +195,10 @@ spells.rogue = {
|
||||
lvl: 13,
|
||||
target: 'party',
|
||||
notes: t('spellRogueToolsOfTradeNotes'),
|
||||
cast (user, target) {
|
||||
each(target, member => {
|
||||
const bonus = statsComputed(user).per - user.stats.buffs.per;
|
||||
if (!member.stats.buffs.per) member.stats.buffs.per = 0;
|
||||
member.stats.buffs.per += Math.ceil(diminishingReturns(bonus, 100, 50));
|
||||
});
|
||||
bulk: true,
|
||||
cast (user, data) {
|
||||
const bonus = statsComputed(user).per - user.stats.buffs.per;
|
||||
data.update = { $inc: { 'stats.buffs.per': Math.ceil(diminishingReturns(bonus, 100, 50)) } };
|
||||
},
|
||||
},
|
||||
stealth: { // Stealth
|
||||
@@ -257,12 +247,10 @@ spells.healer = {
|
||||
lvl: 13,
|
||||
target: 'party',
|
||||
notes: t('spellHealerProtectAuraNotes'),
|
||||
cast (user, target) {
|
||||
each(target, member => {
|
||||
const bonus = statsComputed(user).con - user.stats.buffs.con;
|
||||
if (!member.stats.buffs.con) member.stats.buffs.con = 0;
|
||||
member.stats.buffs.con += Math.ceil(diminishingReturns(bonus, 200, 200));
|
||||
});
|
||||
bulk: true,
|
||||
cast (user, data) {
|
||||
const bonus = statsComputed(user).con - user.stats.buffs.con;
|
||||
data.update = { $inc: { 'stats.buffs.con': Math.ceil(diminishingReturns(bonus, 200, 200)) } };
|
||||
},
|
||||
},
|
||||
healAll: { // Blessing
|
||||
|
||||
@@ -222,6 +222,7 @@ function _getBasicAchievements (user, language) {
|
||||
_addSimple(result, user, { path: 'boneToPick', language });
|
||||
_addSimple(result, user, { path: 'polarPro', language });
|
||||
_addSimple(result, user, { path: 'plantParent', language });
|
||||
_addSimple(result, user, { path: 'dinosaurDynasty', language });
|
||||
|
||||
_addSimpleWithMasterCount(result, user, { path: 'beastMaster', language });
|
||||
_addSimpleWithMasterCount(result, user, { path: 'mountMaster', language });
|
||||
|
||||
@@ -282,6 +282,8 @@ api.postChat = {
|
||||
analyticsObject.groupName = group.name;
|
||||
}
|
||||
|
||||
res.analytics.track('group chat', analyticsObject);
|
||||
|
||||
if (chatUpdated) {
|
||||
res.respond(200, { chat: chatRes.chat });
|
||||
} else {
|
||||
|
||||
@@ -93,7 +93,7 @@ api.inviteToQuest = {
|
||||
user.party.quest.RSVPNeeded = false;
|
||||
user.party.quest.key = questKey;
|
||||
|
||||
await User.update({
|
||||
await User.updateMany({
|
||||
'party._id': group._id,
|
||||
_id: { $ne: user._id },
|
||||
}, {
|
||||
@@ -101,7 +101,7 @@ api.inviteToQuest = {
|
||||
'party.quest.RSVPNeeded': true,
|
||||
'party.quest.key': questKey,
|
||||
},
|
||||
}, { multi: true }).exec();
|
||||
}).exec();
|
||||
|
||||
_.each(members, member => {
|
||||
group.quest.members[member._id] = null;
|
||||
@@ -409,10 +409,9 @@ api.cancelQuest = {
|
||||
const [savedGroup] = await Promise.all([
|
||||
group.save(),
|
||||
newChatMessage.save(),
|
||||
User.update(
|
||||
User.updateMany(
|
||||
{ 'party._id': groupId },
|
||||
Group.cleanQuestParty(),
|
||||
{ multi: true },
|
||||
).exec(),
|
||||
]);
|
||||
|
||||
@@ -467,12 +466,11 @@ api.abortQuest = {
|
||||
});
|
||||
await newChatMessage.save();
|
||||
|
||||
const memberUpdates = User.update({
|
||||
const memberUpdates = User.updateMany({
|
||||
'party._id': groupId,
|
||||
}, Group.cleanQuestParty(),
|
||||
{ multi: true }).exec();
|
||||
}, Group.cleanQuestParty()).exec();
|
||||
|
||||
const questLeaderUpdate = User.update({
|
||||
const questLeaderUpdate = User.updateOne({
|
||||
_id: group.quest.leader,
|
||||
}, {
|
||||
$inc: {
|
||||
|
||||
@@ -227,7 +227,7 @@ api.deleteTag = {
|
||||
const tagFound = find(user.tags, tag => tag.id === req.params.tagId);
|
||||
if (!tagFound) throw new NotFound(res.t('tagNotFound'));
|
||||
|
||||
await user.update({
|
||||
await user.updateOne({
|
||||
$pull: { tags: { id: tagFound.id } },
|
||||
}).exec();
|
||||
|
||||
@@ -237,13 +237,13 @@ api.deleteTag = {
|
||||
user._v += 1;
|
||||
|
||||
// Remove from all the tasks TODO test
|
||||
await Tasks.Task.update({
|
||||
await Tasks.Task.updateMany({
|
||||
userId: user._id,
|
||||
}, {
|
||||
$pull: {
|
||||
tags: tagFound.id,
|
||||
},
|
||||
}, { multi: true }).exec();
|
||||
}).exec();
|
||||
|
||||
res.respond(200, {});
|
||||
},
|
||||
|
||||
@@ -840,7 +840,7 @@ api.moveTask = {
|
||||
// Cannot send $pull and $push on same field in one single op
|
||||
const pullQuery = { $pull: {} };
|
||||
pullQuery.$pull[`tasksOrder.${task.type}s`] = task.id;
|
||||
await owner.update(pullQuery).exec();
|
||||
await owner.updateOne(pullQuery).exec();
|
||||
|
||||
let position = to;
|
||||
if (to === -1) position = order.length - 1; // push to bottom
|
||||
@@ -850,7 +850,7 @@ api.moveTask = {
|
||||
$each: [task._id],
|
||||
$position: position,
|
||||
};
|
||||
await owner.update(updateQuery).exec();
|
||||
await owner.updateOne(updateQuery).exec();
|
||||
|
||||
// Update the user version field manually,
|
||||
// it cannot be updated in the pre update hook
|
||||
@@ -1434,7 +1434,7 @@ api.deleteTask = {
|
||||
|
||||
const pullQuery = { $pull: {} };
|
||||
pullQuery.$pull[`tasksOrder.${task.type}s`] = task._id;
|
||||
const taskOrderUpdate = (challenge || user).update(pullQuery).exec();
|
||||
const taskOrderUpdate = (challenge || user).updateOne(pullQuery).exec();
|
||||
|
||||
// Update the user version field manually,
|
||||
// it cannot be updated in the pre update hook
|
||||
|
||||
@@ -37,7 +37,15 @@ export default function baseModel (schema, options = {}) {
|
||||
});
|
||||
|
||||
schema.pre('update', function preUpdateModel () {
|
||||
this.update({}, { $set: { updatedAt: new Date() } });
|
||||
this.set({}, { $set: { updatedAt: new Date() } });
|
||||
});
|
||||
|
||||
schema.pre('updateOne', function preUpdateModel () {
|
||||
this.set({}, { $set: { updatedAt: new Date() } });
|
||||
});
|
||||
|
||||
schema.pre('updateMany', function preUpdateModel () {
|
||||
this.set({}, { $set: { updatedAt: new Date() } });
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -180,6 +180,7 @@ async function addSubToGroupUser (member, group) {
|
||||
}
|
||||
|
||||
// save unused hourglass and mystery items
|
||||
plan.perkMonthCount = memberPlan.perkMonthCount;
|
||||
plan.consecutive.trinkets = memberPlan.consecutive.trinkets;
|
||||
plan.mysteryItems = memberPlan.mysteryItems;
|
||||
|
||||
|
||||
@@ -253,13 +253,18 @@ async function createSubscription (data) {
|
||||
} = await prepareSubscriptionValues(data);
|
||||
|
||||
// Block sub perks
|
||||
if (months > 0 && (!data.gift || !isNewSubscription)) {
|
||||
if (months > 1 && (!data.gift || !isNewSubscription)) {
|
||||
if (!data.gift && !groupId) {
|
||||
plan.consecutive.offset = block.months;
|
||||
}
|
||||
} else if (months === 1) {
|
||||
plan.consecutive.offset = 0;
|
||||
}
|
||||
if (months > 1 || data.gift) {
|
||||
await plan.incrementPerkCounterAndReward(recipient._id, months);
|
||||
} else {
|
||||
// Make sure the perkMonthCount field is initialized.
|
||||
await plan.incrementPerkCounterAndReward(recipient._id, 0);
|
||||
}
|
||||
|
||||
if (recipient !== group) {
|
||||
|
||||
@@ -21,7 +21,7 @@ const apnProvider = APN_ENABLED ? new apn.Provider({
|
||||
}) : undefined;
|
||||
|
||||
function removePushDevice (user, pushDevice) {
|
||||
return User.update({ _id: user._id }, {
|
||||
return User.updateOne({ _id: user._id }, {
|
||||
$pull: { pushDevices: { regId: pushDevice.regId } },
|
||||
}).exec().catch(err => {
|
||||
logger.error(err, `Error removing pushDevice ${pushDevice.regId} for user ${user._id}`);
|
||||
|
||||
@@ -24,7 +24,7 @@ export function readController (router, controller, overrides = []) {
|
||||
|
||||
// If an authentication middleware is used run getUserLanguage after it, otherwise before
|
||||
// for cron instead use it only if an authentication middleware is present
|
||||
const authMiddlewareIndex = _.findIndex(middlewares, middleware => {
|
||||
let authMiddlewareIndex = _.findIndex(middlewares, middleware => {
|
||||
if (middleware.name.indexOf('authWith') === 0) { // authWith{Headers|Session|Url|...}
|
||||
return true;
|
||||
}
|
||||
@@ -36,6 +36,7 @@ export function readController (router, controller, overrides = []) {
|
||||
// disable caching for all routes with mandatory or optional authentication
|
||||
if (authMiddlewareIndex !== -1) {
|
||||
middlewares.unshift(disableCache);
|
||||
authMiddlewareIndex += 1;
|
||||
}
|
||||
|
||||
if (action.noLanguage !== true) { // unless getting the language is explictly disabled
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
} from '../models/group';
|
||||
import apiError from './apiError';
|
||||
|
||||
const partyMembersFields = 'profile.name stats achievements items.special notifications flags pinnedItems';
|
||||
const partyMembersFields = 'profile.name stats achievements items.special pinnedItems notifications flags';
|
||||
// Excluding notifications and flags from the list of public fields to return.
|
||||
const partyMembersPublicFields = 'profile.name stats achievements items.special';
|
||||
|
||||
@@ -74,12 +74,13 @@ async function castSelfSpell (req, user, spell, quantity = 1) {
|
||||
await user.save();
|
||||
}
|
||||
|
||||
async function castPartySpell (req, party, partyMembers, user, spell, quantity = 1) {
|
||||
async function getPartyMembers (user, party) {
|
||||
let partyMembers;
|
||||
if (!party) {
|
||||
// Act as solo party
|
||||
partyMembers = [user]; // eslint-disable-line no-param-reassign
|
||||
partyMembers = [user];
|
||||
} else {
|
||||
partyMembers = await User // eslint-disable-line no-param-reassign
|
||||
partyMembers = await User
|
||||
.find({
|
||||
'party._id': party._id,
|
||||
_id: { $ne: user._id }, // add separately
|
||||
@@ -89,22 +90,40 @@ async function castPartySpell (req, party, partyMembers, user, spell, quantity =
|
||||
|
||||
partyMembers.unshift(user);
|
||||
}
|
||||
|
||||
for (let i = 0; i < quantity; i += 1) {
|
||||
spell.cast(user, partyMembers, req);
|
||||
}
|
||||
await Promise.all(partyMembers.map(m => m.save()));
|
||||
|
||||
return partyMembers;
|
||||
}
|
||||
|
||||
async function castUserSpell (res, req, party, partyMembers, targetId, user, spell, quantity = 1) {
|
||||
async function castPartySpell (req, party, user, spell, quantity = 1) {
|
||||
let partyMembers;
|
||||
if (spell.bulk) {
|
||||
const data = { };
|
||||
if (party) {
|
||||
data.query = { 'party._id': party._id };
|
||||
} else {
|
||||
data.query = { _id: user._id };
|
||||
}
|
||||
spell.cast(user, data);
|
||||
await User.updateMany(data.query, data.update);
|
||||
await user.save();
|
||||
partyMembers = await getPartyMembers(user, party);
|
||||
} else {
|
||||
partyMembers = await getPartyMembers(user, party);
|
||||
for (let i = 0; i < quantity; i += 1) {
|
||||
spell.cast(user, partyMembers, req);
|
||||
}
|
||||
await Promise.all(partyMembers.map(m => m.save()));
|
||||
}
|
||||
return partyMembers;
|
||||
}
|
||||
|
||||
async function castUserSpell (res, req, party, targetId, user, spell, quantity = 1) {
|
||||
let partyMembers;
|
||||
if (!party && (!targetId || user._id === targetId)) {
|
||||
partyMembers = user; // eslint-disable-line no-param-reassign
|
||||
partyMembers = user;
|
||||
} else {
|
||||
if (!targetId) throw new BadRequest(res.t('targetIdUUID'));
|
||||
if (!party) throw new NotFound(res.t('partyNotFound'));
|
||||
partyMembers = await User // eslint-disable-line no-param-reassign
|
||||
partyMembers = await User
|
||||
.findOne({ _id: targetId, 'party._id': party._id })
|
||||
.select(partyMembersFields)
|
||||
.exec();
|
||||
@@ -195,10 +214,10 @@ async function castSpell (req, res, { isV3 = false }) {
|
||||
let partyMembers;
|
||||
|
||||
if (targetType === 'party') {
|
||||
partyMembers = await castPartySpell(req, party, partyMembers, user, spell, quantity);
|
||||
partyMembers = await castPartySpell(req, party, user, spell, quantity);
|
||||
} else {
|
||||
partyMembers = await castUserSpell(
|
||||
res, req, party, partyMembers,
|
||||
res, req, party,
|
||||
targetId, user, spell, quantity,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -114,7 +114,7 @@ async function createTasks (req, res, options = {}) {
|
||||
};
|
||||
}
|
||||
|
||||
await owner.update(taskOrderUpdateQuery).exec();
|
||||
await owner.updateOne(taskOrderUpdateQuery).exec();
|
||||
|
||||
// tasks with aliases need to be validated asynchronously
|
||||
await validateTaskAlias(toSave, res);
|
||||
|
||||
@@ -121,7 +121,7 @@ async function checkNewInputForProfanity (user, res, newValue) {
|
||||
export async function update (req, res, { isV3 = false }) {
|
||||
const { user } = res.locals;
|
||||
|
||||
const promisesForTagsRemoval = [];
|
||||
let promisesForTagsRemoval = [];
|
||||
|
||||
if (req.body['party.seeking'] !== undefined && req.body['party.seeking'] !== null) {
|
||||
user.invitations.party = {};
|
||||
@@ -130,6 +130,7 @@ export async function update (req, res, { isV3 = false }) {
|
||||
uuid: user._id,
|
||||
hitType: 'event',
|
||||
category: 'behavior',
|
||||
headers: req.headers,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -187,6 +188,7 @@ export async function update (req, res, { isV3 = false }) {
|
||||
uuid: user._id,
|
||||
hitType: 'event',
|
||||
category: 'behavior',
|
||||
headers: req.headers,
|
||||
});
|
||||
} else if (key === 'tags') {
|
||||
if (!Array.isArray(val)) throw new BadRequest('Tag list must be an array.');
|
||||
@@ -218,13 +220,13 @@ export async function update (req, res, { isV3 = false }) {
|
||||
// Remove from all the tasks
|
||||
// NOTE each tag to remove requires a query
|
||||
|
||||
promisesForTagsRemoval.push(removedTagsIds.map(tagId => Tasks.Task.update({
|
||||
promisesForTagsRemoval = removedTagsIds.map(tagId => Tasks.Task.updateMany({
|
||||
userId: user._id,
|
||||
}, {
|
||||
$pull: {
|
||||
tags: tagId,
|
||||
},
|
||||
}, { multi: true }).exec()));
|
||||
}).exec());
|
||||
} else if (key === 'flags.newStuff' && val === false) {
|
||||
// flags.newStuff was removed from the user schema and is only returned for compatibility
|
||||
// reasons but we're keeping the ability to set it in API v3
|
||||
@@ -274,6 +276,7 @@ export async function reset (req, res, { isV3 = false }) {
|
||||
uuid: user._id,
|
||||
hitType: 'event',
|
||||
category: 'behavior',
|
||||
headers: req.headers,
|
||||
});
|
||||
|
||||
res.respond(200, ...resetRes);
|
||||
|
||||
@@ -61,7 +61,7 @@ function sendWebhook (webhook, body, user) {
|
||||
};
|
||||
}
|
||||
|
||||
return User.update({
|
||||
return User.updateOne({
|
||||
_id: user._id,
|
||||
'webhooks.id': webhook.id,
|
||||
}, update).exec();
|
||||
|
||||
@@ -16,7 +16,7 @@ async function checkForActiveCron (user, now) {
|
||||
|
||||
// To avoid double cron we first set _cronSignature
|
||||
// and then check that it's not changed while processing
|
||||
const userUpdateResult = await User.update({
|
||||
const userUpdateResult = await User.updateOne({
|
||||
_id: user._id,
|
||||
$or: [ // Make sure last cron was successful or failed before cronRetryTime
|
||||
{ _cronSignature: 'NOT_RUNNING' },
|
||||
@@ -36,7 +36,7 @@ async function checkForActiveCron (user, now) {
|
||||
}
|
||||
|
||||
async function updateLastCron (user, now) {
|
||||
await User.update({
|
||||
await User.updateOne({
|
||||
_id: user._id,
|
||||
}, {
|
||||
lastCron: now, // setting lastCron now so we don't risk re-running parts of cron if it fails
|
||||
@@ -44,7 +44,7 @@ async function updateLastCron (user, now) {
|
||||
}
|
||||
|
||||
async function unlockUser (user) {
|
||||
await User.update({
|
||||
await User.updateOne({
|
||||
_id: user._id,
|
||||
}, {
|
||||
_cronSignature: 'NOT_RUNNING',
|
||||
@@ -125,7 +125,7 @@ async function cronAsync (req, res) {
|
||||
await Group.processQuestProgress(user, progress);
|
||||
|
||||
// Set _cronSignature, lastCron and auth.timestamps.loggedin to signal end of cron
|
||||
await User.update({
|
||||
await User.updateOne({
|
||||
_id: user._id,
|
||||
}, {
|
||||
$set: {
|
||||
@@ -153,7 +153,7 @@ async function cronAsync (req, res) {
|
||||
// For any other error make sure to reset _cronSignature
|
||||
// so that it doesn't prevent cron from running
|
||||
// at the next request
|
||||
await User.update({
|
||||
await User.updateOne({
|
||||
_id: user._id,
|
||||
}, {
|
||||
_cronSignature: 'NOT_RUNNING',
|
||||
|
||||
@@ -102,7 +102,7 @@ schema.methods.addToUser = async function addChallengeToUser (user) {
|
||||
// Add challenge to users challenges atomically (with a condition that checks that it
|
||||
// is not there already) to prevent multiple concurrent requests from passing through
|
||||
// see https://github.com/HabitRPG/habitica/issues/11295
|
||||
const result = await User.update(
|
||||
const result = await User.updateOne(
|
||||
{
|
||||
_id: user._id,
|
||||
challenges: { $nin: [this._id] },
|
||||
@@ -249,7 +249,7 @@ async function _addTaskFn (challenge, tasks, memberId) {
|
||||
},
|
||||
};
|
||||
const updateUserParams = { ...updateTasksOrderQ, ...addToChallengeTagSet };
|
||||
toSave.unshift(User.update({ _id: memberId }, updateUserParams).exec());
|
||||
toSave.unshift(User.updateOne({ _id: memberId }, updateUserParams).exec());
|
||||
|
||||
return Promise.all(toSave);
|
||||
}
|
||||
@@ -278,11 +278,11 @@ schema.methods.updateTask = async function challengeUpdateTask (task) {
|
||||
const taskSchema = Tasks[task.type];
|
||||
// Updating instead of loading and saving for performances,
|
||||
// risks becoming a problem if we introduce more complexity in tasks
|
||||
await taskSchema.update({
|
||||
await taskSchema.updateMany({
|
||||
userId: { $exists: true },
|
||||
'challenge.id': challenge.id,
|
||||
'challenge.taskId': task._id,
|
||||
}, updateCmd, { multi: true }).exec();
|
||||
}, updateCmd).exec();
|
||||
};
|
||||
|
||||
// Remove a task from challenge members
|
||||
@@ -290,13 +290,13 @@ schema.methods.removeTask = async function challengeRemoveTask (task) {
|
||||
const challenge = this;
|
||||
|
||||
// Set the task as broken
|
||||
await Tasks.Task.update({
|
||||
await Tasks.Task.updateMany({
|
||||
userId: { $exists: true },
|
||||
'challenge.id': challenge.id,
|
||||
'challenge.taskId': task._id,
|
||||
}, {
|
||||
$set: { 'challenge.broken': 'TASK_DELETED' },
|
||||
}, { multi: true }).exec();
|
||||
}).exec();
|
||||
};
|
||||
|
||||
// Unlink challenges tasks (and the challenge itself) from user. TODO rename to 'leave'
|
||||
@@ -311,9 +311,9 @@ schema.methods.unlinkTasks = async function challengeUnlinkTasks (user, keep, sa
|
||||
this.memberCount -= 1;
|
||||
|
||||
if (keep === 'keep-all') {
|
||||
await Tasks.Task.update(findQuery, {
|
||||
await Tasks.Task.updateMany(findQuery, {
|
||||
$set: { challenge: {} },
|
||||
}, { multi: true }).exec();
|
||||
}).exec();
|
||||
|
||||
const promises = [this.save()];
|
||||
|
||||
@@ -356,11 +356,12 @@ schema.methods.closeChal = async function closeChal (broken = {}) {
|
||||
|
||||
// Refund the leader if the challenge is deleted (no winner chosen)
|
||||
if (brokenReason === 'CHALLENGE_DELETED') {
|
||||
await User.update({ _id: challenge.leader }, { $inc: { balance: challenge.prize / 4 } }).exec();
|
||||
await User.updateOne({ _id: challenge.leader }, { $inc: { balance: challenge.prize / 4 } })
|
||||
.exec();
|
||||
}
|
||||
|
||||
// Update the challengeCount on the group
|
||||
await Group.update({ _id: challenge.group }, { $inc: { challengeCount: -1 } }).exec();
|
||||
await Group.updateOne({ _id: challenge.group }, { $inc: { challengeCount: -1 } }).exec();
|
||||
|
||||
// Award prize to winner and notify
|
||||
if (winner) {
|
||||
@@ -370,7 +371,7 @@ schema.methods.closeChal = async function closeChal (broken = {}) {
|
||||
// reimburse the leader
|
||||
const winnerCanGetGems = await winner.canGetGems();
|
||||
if (!winnerCanGetGems) {
|
||||
await User.update(
|
||||
await User.updateOne(
|
||||
{ _id: challenge.leader },
|
||||
{ $inc: { balance: challenge.prize / 4 } },
|
||||
).exec();
|
||||
@@ -408,22 +409,22 @@ schema.methods.closeChal = async function closeChal (broken = {}) {
|
||||
Tasks.Task.remove({ 'challenge.id': challenge._id, userId: { $exists: false } }).exec(),
|
||||
// Set the challenge tag to non-challenge status
|
||||
// and remove the challenge from the user's challenges
|
||||
User.update({
|
||||
User.updateMany({
|
||||
challenges: challenge._id,
|
||||
'tags.id': challenge._id,
|
||||
}, {
|
||||
$set: { 'tags.$.challenge': false },
|
||||
$pull: { challenges: challenge._id },
|
||||
}, { multi: true }).exec(),
|
||||
}).exec(),
|
||||
// Break users' tasks
|
||||
Tasks.Task.update({
|
||||
Tasks.Task.updateMany({
|
||||
'challenge.id': challenge._id,
|
||||
}, {
|
||||
$set: {
|
||||
'challenge.broken': brokenReason,
|
||||
'challenge.winner': winner && winner.profile.name,
|
||||
},
|
||||
}, { multi: true }).exec(),
|
||||
}).exec(),
|
||||
];
|
||||
|
||||
Promise.all(backgroundTasks);
|
||||
|
||||
@@ -268,10 +268,13 @@ schema.statics.getGroup = async function getGroup (options = {}) {
|
||||
if (groupId === user.party._id) {
|
||||
// reset party object to default state
|
||||
user.party = {};
|
||||
await user.save();
|
||||
} else {
|
||||
removeFromArray(user.guilds, groupId);
|
||||
const item = removeFromArray(user.guilds, groupId);
|
||||
if (item) {
|
||||
await user.save();
|
||||
}
|
||||
}
|
||||
await user.save();
|
||||
}
|
||||
|
||||
return group;
|
||||
@@ -659,7 +662,7 @@ schema.methods.handleQuestInvitation = async function handleQuestInvitation (use
|
||||
// to prevent multiple concurrent requests overriding updates
|
||||
// see https://github.com/HabitRPG/habitica/issues/11398
|
||||
const Group = this.constructor;
|
||||
const result = await Group.update(
|
||||
const result = await Group.updateOne(
|
||||
{
|
||||
_id: this._id,
|
||||
[`quest.members.${user._id}`]: { $type: 10 }, // match BSON Type Null (type number 10)
|
||||
@@ -707,7 +710,7 @@ schema.methods.startQuest = async function startQuest (user) {
|
||||
|
||||
// Persist quest.members early to avoid simultaneous handling of accept/reject
|
||||
// while processing the rest of this script
|
||||
await this.update({ $set: { 'quest.members': this.quest.members } }).exec();
|
||||
await this.updateOne({ $set: { 'quest.members': this.quest.members } }).exec();
|
||||
|
||||
const nonUserQuestMembers = _.keys(this.quest.members);
|
||||
removeFromArray(nonUserQuestMembers, user._id);
|
||||
@@ -747,7 +750,7 @@ schema.methods.startQuest = async function startQuest (user) {
|
||||
user.markModified('items.quests');
|
||||
promises.push(user.save());
|
||||
} else { // another user is starting the quest, update the leader separately
|
||||
promises.push(User.update({ _id: this.quest.leader }, {
|
||||
promises.push(User.updateOne({ _id: this.quest.leader }, {
|
||||
$inc: {
|
||||
[`items.quests.${this.quest.key}`]: -1,
|
||||
},
|
||||
@@ -755,7 +758,7 @@ schema.methods.startQuest = async function startQuest (user) {
|
||||
}
|
||||
|
||||
// update the remaining users
|
||||
promises.push(User.update({
|
||||
promises.push(User.updateMany({
|
||||
_id: { $in: nonUserQuestMembers },
|
||||
}, {
|
||||
$set: {
|
||||
@@ -763,16 +766,15 @@ schema.methods.startQuest = async function startQuest (user) {
|
||||
'party.quest.progress.down': 0,
|
||||
'party.quest.completed': null,
|
||||
},
|
||||
}, { multi: true }).exec());
|
||||
}).exec());
|
||||
|
||||
await Promise.all(promises);
|
||||
|
||||
// update the users who are not participating
|
||||
// Do not block updates
|
||||
User.update({
|
||||
User.updateMany({
|
||||
_id: { $in: nonMembers },
|
||||
}, _cleanQuestParty(),
|
||||
{ multi: true }).exec();
|
||||
}, _cleanQuestParty()).exec();
|
||||
|
||||
const newMessage = this.sendChat({
|
||||
message: `\`${shared.i18n.t('chatQuestStarted', { questName: quest.text('en') }, 'en')}\``,
|
||||
@@ -903,7 +905,7 @@ function _getUserUpdateForQuestReward (itemToAward, allAwardedItems) {
|
||||
async function _updateUserWithRetries (userId, updates, numTry = 1, query = {}) {
|
||||
query._id = userId;
|
||||
try {
|
||||
return await User.update(query, updates).exec();
|
||||
return await User.updateOne(query, updates).exec();
|
||||
} catch (err) {
|
||||
if (numTry < MAX_UPDATE_RETRIES) {
|
||||
numTry += 1; // eslint-disable-line no-param-reassign
|
||||
@@ -949,7 +951,7 @@ schema.methods.finishQuest = async function finishQuest (quest) {
|
||||
this.markModified('quest');
|
||||
|
||||
if (this._id === TAVERN_ID) {
|
||||
return User.update({}, updates, { multi: true }).exec();
|
||||
return User.updateMany({}, updates).exec();
|
||||
}
|
||||
|
||||
const promises = participants.map(userId => {
|
||||
@@ -1389,10 +1391,10 @@ schema.methods.leave = async function leaveGroup (user, keep = 'keep-all', keepC
|
||||
const userUpdate = { $pull: { 'preferences.tasks.mirrorGroupTasks': group._id } };
|
||||
if (group.type === 'guild') {
|
||||
userUpdate.$pull.guilds = group._id;
|
||||
promises.push(User.update({ _id: user._id }, userUpdate).exec());
|
||||
promises.push(User.updateOne({ _id: user._id }, userUpdate).exec());
|
||||
} else {
|
||||
userUpdate.$set = { party: {} };
|
||||
promises.push(User.update({ _id: user._id }, userUpdate).exec());
|
||||
promises.push(User.updateOne({ _id: user._id }, userUpdate).exec());
|
||||
|
||||
update.$unset = { [`quest.members.${user._id}`]: 1 };
|
||||
}
|
||||
@@ -1508,7 +1510,7 @@ schema.methods.unlinkTask = async function groupUnlinkTask (
|
||||
const promises = [unlinkingTask.save()];
|
||||
|
||||
if (keep === 'keep-all') {
|
||||
await Tasks.Task.update(findQuery, {
|
||||
await Tasks.Task.updateOne(findQuery, {
|
||||
$set: { group: {} },
|
||||
}).exec();
|
||||
|
||||
|
||||
@@ -56,13 +56,20 @@ schema.methods.incrementPerkCounterAndReward = async function incrementPerkCount
|
||||
if (typeof adding === 'string' || adding instanceof String) {
|
||||
addingNumber = parseInt(adding, 10);
|
||||
}
|
||||
const isSingleMonthPlan = this.planId === 'basic_earned' || this.planId === 'group_plan_auto' || this.planId === 'group_monthly';
|
||||
// if perkMonthCount wasn't used before, initialize it.
|
||||
if (this.perkMonthCount === undefined || this.perkMonthCount === -1) {
|
||||
if (this.planId === 'basic_earned') {
|
||||
if (isSingleMonthPlan && this.consecutive.count > 0) {
|
||||
this.perkMonthCount = (this.consecutive.count - 1) % SUBSCRIPTION_BASIC_BLOCK_LENGTH;
|
||||
} else {
|
||||
this.perkMonthCount = 0;
|
||||
}
|
||||
} else if (isSingleMonthPlan) {
|
||||
const expectedPerkMonthCount = (this.consecutive.count - 1) % SUBSCRIPTION_BASIC_BLOCK_LENGTH;
|
||||
if (this.perkMonthCount === (expectedPerkMonthCount - 1)) {
|
||||
// User was affected by a bug that makes their perkMonthCount off by one
|
||||
this.perkMonthCount += 1;
|
||||
}
|
||||
}
|
||||
this.perkMonthCount += addingNumber;
|
||||
|
||||
|
||||
@@ -392,6 +392,13 @@ schema.pre('update', function preUpdateUser () {
|
||||
this.update({}, { $inc: { _v: 1 } });
|
||||
});
|
||||
|
||||
schema.pre('updateOne', function preUpdateUser () {
|
||||
this.updateOne({}, { $inc: { _v: 1 } });
|
||||
});
|
||||
schema.pre('updateMany', function preUpdateUser () {
|
||||
this.updateMany({}, { $inc: { _v: 1 } });
|
||||
});
|
||||
|
||||
schema.post('save', function postSaveUser () {
|
||||
// Send a webhook notification when the user has leveled up
|
||||
if (this._tmp && this._tmp.leveledUp && this._tmp.leveledUp.length > 0) {
|
||||
|
||||
@@ -225,10 +225,9 @@ schema.statics.pushNotification = async function pushNotification (
|
||||
throw validationResult;
|
||||
}
|
||||
|
||||
await this.update(
|
||||
await this.updateMany(
|
||||
query,
|
||||
{ $push: { notifications: newNotification.toObject() } },
|
||||
{ multi: true },
|
||||
).exec();
|
||||
};
|
||||
|
||||
@@ -274,13 +273,12 @@ schema.statics.addAchievementUpdate = async function addAchievementUpdate (query
|
||||
const validationResult = newNotification.validateSync();
|
||||
if (validationResult) throw validationResult;
|
||||
|
||||
await this.update(
|
||||
await this.updateMany(
|
||||
query,
|
||||
{
|
||||
$push: { notifications: newNotification.toObject() },
|
||||
$set: { [`achievements.${achievement}`]: true },
|
||||
},
|
||||
{ multi: true },
|
||||
).exec();
|
||||
};
|
||||
|
||||
|
||||
@@ -154,6 +154,7 @@ export const UserSchema = new Schema({
|
||||
boneToPick: Boolean,
|
||||
polarPro: Boolean,
|
||||
plantParent: Boolean,
|
||||
dinosaurDynasty: Boolean,
|
||||
// Onboarding Guide
|
||||
createdTask: Boolean,
|
||||
completedTask: Boolean,
|
||||
@@ -534,7 +535,7 @@ export const UserSchema = new Schema({
|
||||
stickyHeader: { $type: Boolean, default: true },
|
||||
disableClasses: { $type: Boolean, default: false },
|
||||
newTaskEdit: { $type: Boolean, default: false },
|
||||
// deprecated, unused
|
||||
// not used anymore, now the current filter is saved in preferences.activeFilter
|
||||
dailyDueDefaultView: { $type: Boolean, default: false },
|
||||
// deprecated, unused
|
||||
advancedCollapsed: { $type: Boolean, default: false },
|
||||
@@ -598,6 +599,12 @@ export const UserSchema = new Schema({
|
||||
mirrorGroupTasks: [
|
||||
{ $type: String, validate: [v => validator.isUUID(v), 'Invalid group UUID.'], ref: 'Group' },
|
||||
],
|
||||
activeFilter: {
|
||||
habit: { $type: String, default: 'all' },
|
||||
daily: { $type: String, default: 'all' },
|
||||
todo: { $type: String, default: 'remaining' },
|
||||
reward: { $type: String, default: 'all' },
|
||||
},
|
||||
},
|
||||
improvementCategories: {
|
||||
$type: Array,
|
||||
|
||||
Reference in New Issue
Block a user