mirror of
https://github.com/HabitRPG/habitica.git
synced 2026-05-20 03:14:01 -05:00
Compare commits
158 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 08d71cc0bb | |||
| c4f870c421 | |||
| ac7c8e0eb6 | |||
| 7c9df3b32f | |||
| edefae72bd | |||
| 132ecc8bb1 | |||
| ca5b02d0ea | |||
| d8a7cad1a1 | |||
| fa83d1a9cf | |||
| d94851f759 | |||
| 2137c190b3 | |||
| 428e693711 | |||
| a7aa489960 | |||
| c917c7c4a9 | |||
| e661838ed7 | |||
| af09b6b454 | |||
| f8b3891a0c | |||
| 631d18244b | |||
| 7883ba8228 | |||
| 605c2265c5 | |||
| 1bb0319012 | |||
| 1f0fd7d8b4 | |||
| 6b4b53a430 | |||
| 331ea18c42 | |||
| db53d87cba | |||
| ebebbbaac5 | |||
| 63376b918e | |||
| ba96cd6e24 | |||
| 9e0e2a83be | |||
| 1fa926ac04 | |||
| c71f0b3fda | |||
| e8f5958f77 | |||
| 2803db73e5 | |||
| 0f9adf6675 | |||
| 63414a80fe | |||
| e73e8bfb9e | |||
| 694fe5a273 | |||
| 82ebe71eb4 | |||
| 58d58ff962 | |||
| 04dcb27501 | |||
| c9395ba1ca | |||
| e9845e7b01 | |||
| faa7ff6328 | |||
| 50cc7ee09a | |||
| db56134832 | |||
| 1ea954ab10 | |||
| e405372319 | |||
| 8f64afe9df | |||
| bf0e640fa6 | |||
| a6792a4f08 | |||
| 0007736f5c | |||
| d564944507 | |||
| b679cfb935 | |||
| 464e4f10b2 | |||
| 647b27c55f | |||
| ac4e6490d9 | |||
| a3784e98a3 | |||
| 5931f02692 | |||
| e21aa074e4 | |||
| fd038bd150 | |||
| 699be3a3cc | |||
| bb7e0e22ec | |||
| a79a088a8f | |||
| 60c0e6b3df | |||
| 43c10f75c3 | |||
| d4e3e83d46 | |||
| 013f8bcca7 | |||
| ebd0cb72de | |||
| c44b1670cf | |||
| 39477c6f11 | |||
| 1371b80635 | |||
| 9fa355fbcc | |||
| b594d2bb29 | |||
| a8b52ab656 | |||
| cce6d91611 | |||
| 3109a03055 | |||
| 14518b8213 | |||
| 3ad31c7cd0 | |||
| bfa6d24e47 | |||
| 8c88f56d08 | |||
| 1df3f9d9f3 | |||
| b5a0dad7f7 | |||
| 9b34c3e11a | |||
| 9d61bd724a | |||
| 13c21139dd | |||
| 8e85de53cb | |||
| 0cae808b7e | |||
| 81be8316a0 | |||
| d7071d6b4d | |||
| 150cd16b1c | |||
| 1532f8f774 | |||
| aa4426c800 | |||
| 0140b9beb7 | |||
| 7697d87358 | |||
| c2ced5c925 | |||
| b09ae3f053 | |||
| 4c60371ebd | |||
| 16be591ed8 | |||
| f75a4f6982 | |||
| 330c3e1bf6 | |||
| 0ba3cd3bdf | |||
| 2cfe11619a | |||
| 7607c67070 | |||
| d394858022 | |||
| 26f5ef093f | |||
| 714319d67b | |||
| cbaa3180cc | |||
| b4866fd3b1 | |||
| 86646bbbdb | |||
| 282abecd21 | |||
| ba61c91296 | |||
| d49736dd69 | |||
| 16b766beef | |||
| a26c5906d6 | |||
| 08a5bff815 | |||
| 9706d7ac64 | |||
| 93564c5d52 | |||
| 2e94bfc489 | |||
| 5c8a3f7771 | |||
| 8ac03e311b | |||
| f84b5f163c | |||
| ad118095ef | |||
| 7ecad94a51 | |||
| 328b37322e | |||
| 81f7fbc2d5 | |||
| 9590ce939a | |||
| fff6fbfbd6 | |||
| 52abf8acf3 | |||
| bc61443246 | |||
| cd8594a8b9 | |||
| f3600f64e8 | |||
| 752cd57bb1 | |||
| 5d6bf131f4 | |||
| 8445f45b31 | |||
| 7dab47db16 | |||
| a88ca5a1a8 | |||
| c91d115793 | |||
| e3502bd280 | |||
| 8b5ff7c2f9 | |||
| 3ac260026b | |||
| 80e7fda8ef | |||
| 1e7ea399b1 | |||
| 9c889a42aa | |||
| 952b99599b | |||
| 973fa2edc2 | |||
| 5e04040f5f | |||
| 2fc9480ae9 | |||
| 429afc1e71 | |||
| 80da313844 | |||
| de057dc1b2 | |||
| e1398e8d7c | |||
| 7e2a35d7a9 | |||
| df5d1e95d1 | |||
| f4e8bf9c2e | |||
| 9c7f1ae630 | |||
| 8d73e2949a | |||
| 9228b070fa | |||
| 8d9602fb16 |
+1
-1
Submodule habitica-images updated: 109539e445...69724d88ef
@@ -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
|
||||
}
|
||||
};
|
||||
@@ -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
|
||||
}
|
||||
};
|
||||
@@ -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
|
||||
}
|
||||
};
|
||||
Generated
+470
-884
File diff suppressed because it is too large
Load Diff
+10
-10
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "habitica",
|
||||
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
|
||||
"version": "4.277.4",
|
||||
"version": "5.4.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"
|
||||
},
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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: {
|
||||
|
||||
-14
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
});
|
||||
|
||||
@@ -18,6 +18,7 @@ describe('PUT /challenges/:challengeId', () => {
|
||||
privacy: 'private',
|
||||
},
|
||||
members: 1,
|
||||
upgradeToGroupPlan: true,
|
||||
});
|
||||
|
||||
privateGuild = group;
|
||||
|
||||
@@ -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/`);
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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: `<https://habitrpg.github.io/flag-o-rama/?groupId=${group._id}&chatId=${message.id}|Flag this message.>`,
|
||||
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: `<https://habitrpg.github.io/flag-o-rama/?groupId=${group._id}&chatId=${message.id}|Flag this message.> ${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',
|
||||
});
|
||||
|
||||
|
||||
@@ -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') });
|
||||
});
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
+16
-17
@@ -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}`);
|
||||
|
||||
|
||||
@@ -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(),
|
||||
|
||||
+16
-17
@@ -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}`);
|
||||
|
||||
|
||||
@@ -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`))
|
||||
|
||||
@@ -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`))
|
||||
|
||||
@@ -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'),
|
||||
|
||||
@@ -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`))
|
||||
|
||||
@@ -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`))
|
||||
|
||||
@@ -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`))
|
||||
|
||||
@@ -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`))
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -14,8 +14,6 @@ module.exports = {
|
||||
// TODO find a way to let eslint understand webpack aliases
|
||||
'import/no-unresolved': 'off',
|
||||
'import/extensions': 'off',
|
||||
'vue/component-tags-order': 'off',
|
||||
'vue/no-mutating-props': 'off',
|
||||
'vue/no-v-html': 'off',
|
||||
'vue/html-self-closing': ['error', {
|
||||
html: {
|
||||
|
||||
Generated
+82
-61
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -94,6 +94,12 @@
|
||||
height: 90px;
|
||||
}
|
||||
|
||||
.back_special_heroicAureole {
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
background: url("https://habitica-assets.s3.amazonaws.com/mobileApp/images/back_special_heroicAureole.gif") no-repeat;
|
||||
}
|
||||
|
||||
.head_special_0 {
|
||||
background: url("https://habitica-assets.s3.amazonaws.com/mobileApp/images/BackerOnly-Equip-ShadeHelmet.gif") no-repeat;
|
||||
}
|
||||
@@ -192,14 +198,6 @@
|
||||
background: url("https://habitica-assets.s3.amazonaws.com/mobileApp/images/background_steamworks.gif") no-repeat;
|
||||
}
|
||||
|
||||
/* FIXME figure out how to handle customize menu!!
|
||||
.customize-menu .f_head_0 {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
background-position: -1917px -9px;
|
||||
}
|
||||
*/
|
||||
|
||||
[class*="Mount_Head_"],
|
||||
[class*="Mount_Body_"] {
|
||||
margin-top:18px; /* Sprite accommodates 105x123 box */
|
||||
|
||||
@@ -635,6 +635,11 @@
|
||||
width: 141px;
|
||||
height: 147px;
|
||||
}
|
||||
.background_baobab_forest {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/background_baobab_forest.png');
|
||||
width: 141px;
|
||||
height: 147px;
|
||||
}
|
||||
.background_bayou {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/background_bayou.png');
|
||||
width: 141px;
|
||||
@@ -820,6 +825,11 @@
|
||||
width: 141px;
|
||||
height: 147px;
|
||||
}
|
||||
.background_covered_bridge_in_autumn {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/background_covered_bridge_in_autumn.png');
|
||||
width: 141px;
|
||||
height: 147px;
|
||||
}
|
||||
.background_cozy_barn {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/background_cozy_barn.png');
|
||||
width: 141px;
|
||||
@@ -1569,6 +1579,11 @@
|
||||
width: 141px;
|
||||
height: 147px;
|
||||
}
|
||||
.background_moving_day {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/background_moving_day.png');
|
||||
width: 141px;
|
||||
height: 147px;
|
||||
}
|
||||
.background_mystical_observatory {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/background_mystical_observatory.png');
|
||||
width: 141px;
|
||||
@@ -2376,6 +2391,11 @@
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.icon_background_baobab_forest {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_background_baobab_forest.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.icon_background_bayou {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_background_bayou.png');
|
||||
width: 68px;
|
||||
@@ -2566,6 +2586,11 @@
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.icon_background_covered_bridge_in_autumn {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_background_covered_bridge_in_autumn.png');
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
.icon_background_cozy_barn {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_background_cozy_barn.png');
|
||||
width: 68px;
|
||||
@@ -3315,6 +3340,11 @@
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.icon_background_moving_day {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_background_moving_day.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.icon_background_mystical_observatory {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_background_mystical_observatory.png');
|
||||
width: 68px;
|
||||
@@ -19525,6 +19555,11 @@
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.shield_armoire_bucket {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shield_armoire_bucket.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.shield_armoire_chocolateFood {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shield_armoire_chocolateFood.png');
|
||||
width: 90px;
|
||||
@@ -20895,6 +20930,11 @@
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_shield_armoire_bucket {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_shield_armoire_bucket.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_shield_armoire_chocolateFood {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_shield_armoire_chocolateFood.png');
|
||||
width: 68px;
|
||||
@@ -21290,6 +21330,11 @@
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_weapon_armoire_cleaningCloth {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_weapon_armoire_cleaningCloth.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_weapon_armoire_clubOfClubs {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_weapon_armoire_clubOfClubs.png');
|
||||
width: 68px;
|
||||
@@ -21480,6 +21525,11 @@
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_weapon_armoire_mop {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_weapon_armoire_mop.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_weapon_armoire_mythmakerSword {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_weapon_armoire_mythmakerSword.png');
|
||||
width: 68px;
|
||||
@@ -22230,6 +22280,11 @@
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.weapon_armoire_cleaningCloth {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/weapon_armoire_cleaningCloth.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.weapon_armoire_clubOfClubs {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/weapon_armoire_clubOfClubs.png');
|
||||
width: 114px;
|
||||
@@ -22420,6 +22475,11 @@
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.weapon_armoire_mop {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/weapon_armoire_mop.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.weapon_armoire_mythmakerSword {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/weapon_armoire_mythmakerSword.png');
|
||||
width: 90px;
|
||||
@@ -22685,6 +22745,11 @@
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.broad_armor_special_heroicTunic {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_special_heroicTunic.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.broad_armor_special_lunarWarriorArmor {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_special_lunarWarriorArmor.png');
|
||||
width: 90px;
|
||||
@@ -22870,6 +22935,11 @@
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_armor_special_heroicTunic {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_armor_special_heroicTunic.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_armor_special_lunarWarriorArmor {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_armor_special_lunarWarriorArmor.png');
|
||||
width: 68px;
|
||||
@@ -23045,6 +23115,11 @@
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.slim_armor_special_heroicTunic {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_special_heroicTunic.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.slim_armor_special_lunarWarriorArmor {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_special_lunarWarriorArmor.png');
|
||||
width: 90px;
|
||||
@@ -23275,6 +23350,11 @@
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_back_special_heroicAureole {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_back_special_heroicAureole.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_back_special_lionTail {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_back_special_lionTail.png');
|
||||
width: 68px;
|
||||
@@ -28225,11 +28305,6 @@
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.set_mystery_202304 {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/set_mystery_202304.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_armor_mystery_202304 {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_armor_mystery_202304.png');
|
||||
width: 68px;
|
||||
@@ -28240,6 +28315,11 @@
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_set_mystery_202304 {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_set_mystery_202304.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.slim_armor_mystery_202304 {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_mystery_202304.png');
|
||||
width: 114px;
|
||||
@@ -28355,6 +28435,31 @@
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.back_mystery_202309 {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/back_mystery_202309.png');
|
||||
width: 117px;
|
||||
height: 120px;
|
||||
}
|
||||
.headAccessory_mystery_202309 {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_mystery_202309.png');
|
||||
width: 117px;
|
||||
height: 120px;
|
||||
}
|
||||
.shop_back_mystery_202309 {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_back_mystery_202309.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_headAccessory_mystery_202309 {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_headAccessory_mystery_202309.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_set_mystery_202309 {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_set_mystery_202309.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.broad_armor_mystery_301404 {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_mystery_301404.png');
|
||||
width: 90px;
|
||||
@@ -34417,6 +34522,11 @@
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.headAccessory_special_heroicCirclet {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_heroicCirclet.png');
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
}
|
||||
.headAccessory_special_lionEars {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_special_lionEars.png');
|
||||
width: 90px;
|
||||
@@ -34522,6 +34632,11 @@
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_headAccessory_special_heroicCirclet {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_headAccessory_special_heroicCirclet.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.shop_headAccessory_special_lionEars {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_headAccessory_special_lionEars.png');
|
||||
width: 68px;
|
||||
@@ -35908,6 +36023,41 @@
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.heroic_set_icon {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/heroic_set_icon.png');
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
}
|
||||
.icon_pet_veteran_bear {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_pet_veteran_bear.png');
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
}
|
||||
.icon_pet_veteran_dragon {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_pet_veteran_dragon.png');
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
}
|
||||
.icon_pet_veteran_fox {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_pet_veteran_fox.png');
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
}
|
||||
.icon_pet_veteran_lion {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_pet_veteran_lion.png');
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
}
|
||||
.icon_pet_veteran_tiger {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_pet_veteran_tiger.png');
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
}
|
||||
.icon_pet_veteran_wolf {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/icon_pet_veteran_wolf.png');
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
}
|
||||
.notif_head_special_nye {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/notif_head_special_nye.png');
|
||||
width: 28px;
|
||||
@@ -55208,6 +55358,11 @@
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Dragon-Veteran {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-Dragon-Veteran.png');
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
.Pet-Dragon-VirtualPet {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-Dragon-VirtualPet.png');
|
||||
width: 81px;
|
||||
|
||||
@@ -1,26 +1,35 @@
|
||||
<template>
|
||||
<div class="row">
|
||||
<div class="col-12 text-center">
|
||||
<div class="col-6 text-center mx-auto mb-5">
|
||||
<!-- @TODO i18n. How to setup the strings with the router-link inside?-->
|
||||
<img
|
||||
class="not-found-img"
|
||||
:class="retiredChatPage ? 'mt-5' : 'image-404'"
|
||||
src="~@/assets/images/404.png"
|
||||
>
|
||||
<h1 class="not-found">
|
||||
Sometimes even the bravest adventurer gets lost.
|
||||
</h1>
|
||||
<h2 class="not-found">
|
||||
Looks like this link is broken or the page may have moved, sorry!
|
||||
</h2>
|
||||
<h2 class="not-found">
|
||||
Head back to the
|
||||
<router-link to="/">
|
||||
Homepage
|
||||
</router-link>or
|
||||
<router-link :to="contactUsLink">
|
||||
Contact Us
|
||||
</router-link>about the issue.
|
||||
</h2>
|
||||
<div v-if="retiredChatPage">
|
||||
<h1>
|
||||
{{ $t('tavernDiscontinued') }}
|
||||
</h1>
|
||||
<p>{{ $t('tavernDiscontinuedDetail') }}</p>
|
||||
<p v-html="$t('tavernDiscontinuedLinks')"></p>
|
||||
</div>
|
||||
<div v-else>
|
||||
<h1>
|
||||
Sometimes even the bravest adventurer gets lost.
|
||||
</h1>
|
||||
<p class="mb-0">
|
||||
Looks like this link is broken or the page may have moved, sorry!
|
||||
</p>
|
||||
<p>
|
||||
Head back to the
|
||||
<router-link to="/">
|
||||
Homepage
|
||||
</router-link>or
|
||||
<router-link :to="contactUsLink">
|
||||
Contact Us
|
||||
</router-link>about the issue.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -37,6 +46,9 @@ export default {
|
||||
}
|
||||
return { name: 'contact' };
|
||||
},
|
||||
retiredChatPage () {
|
||||
return this.$route.fullPath.indexOf('/groups') !== -1;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -44,28 +56,20 @@ export default {
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
|
||||
.col-12 {
|
||||
margin-bottom: 120px;
|
||||
}
|
||||
|
||||
.not-found-img {
|
||||
margin-top: 152px;
|
||||
margin-bottom: 42px;
|
||||
}
|
||||
|
||||
h1.not-found {
|
||||
line-height: 1.33;
|
||||
h1, .static-wrapper h1 {
|
||||
color: $purple-200;
|
||||
margin-bottom: 8px;
|
||||
font-weight: normal;
|
||||
margin-top: 0px;
|
||||
line-height: 1.33;
|
||||
margin-top: 3rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
h2.not-found {
|
||||
line-height: 1.4;
|
||||
font-weight: normal;
|
||||
color: $gray-200;
|
||||
margin-bottom: 0px;
|
||||
margin-top: 0px;
|
||||
p {
|
||||
font-size: 16px;
|
||||
line-height: 1.75;
|
||||
}
|
||||
|
||||
.image-404 {
|
||||
margin-top: 104px;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
<template>
|
||||
<b-modal
|
||||
id="testing"
|
||||
:title="$t('guildReminderTitle')"
|
||||
size="lg"
|
||||
:hide-footer="true"
|
||||
>
|
||||
<div class="modal-body text-center">
|
||||
<br>
|
||||
<div class="scene_guilds"></div>
|
||||
<br>
|
||||
<h4>{{ $t('guildReminderText1') }}</h4>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-6 text-center">
|
||||
<button
|
||||
class="btn btn-secondary"
|
||||
@click="close()"
|
||||
>
|
||||
{{ $t('guildReminderDismiss') }}
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="col-6 text-center"
|
||||
@click="close()"
|
||||
>
|
||||
<div
|
||||
class="btn btn-primary"
|
||||
@click="takeMethere()"
|
||||
>
|
||||
{{ $t('guildReminderCTA') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</b-modal>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.scene_guilds {
|
||||
margin: 0 auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
methods: {
|
||||
close () {
|
||||
this.$root.$emit('bv::hide::modal', 'testing');
|
||||
},
|
||||
takeMethere () {
|
||||
this.$router.push('/groups/discovery');
|
||||
this.close();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -1,58 +0,0 @@
|
||||
<template>
|
||||
<b-modal
|
||||
id="testingletiant"
|
||||
:title="$t('guildReminderTitle')"
|
||||
size="lg"
|
||||
:hide-footer="true"
|
||||
>
|
||||
<div class="modal-content"></div>
|
||||
<div class="modal-body text-center">
|
||||
<br>
|
||||
<div class="scene_guilds"></div>
|
||||
<br>
|
||||
<h4>{{ $t('guildReminderText2') }}</h4>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-6 text-center">
|
||||
<button
|
||||
class="btn btn-secondary"
|
||||
@click="close()"
|
||||
>
|
||||
{{ $t('guildReminderDismiss') }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-6 text-center">
|
||||
<div
|
||||
class="btn btn-primary"
|
||||
@click="takeMethere()"
|
||||
>
|
||||
{{ $t('guildReminderCTA') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</b-modal>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.scene_guilds {
|
||||
margin: 0 auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
methods: {
|
||||
close () {
|
||||
this.$root.$emit('bv::hide::modal', 'testingletiant');
|
||||
},
|
||||
takeMethere () {
|
||||
this.$router.push('/groups/discovery');
|
||||
this.close();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<div
|
||||
v-if="member.preferences"
|
||||
class="avatar"
|
||||
:style="{width, height, paddingTop}"
|
||||
:class="backgroundClass"
|
||||
@@ -184,9 +185,11 @@ export default {
|
||||
currentEventList: 'worldState.data.currentEventList',
|
||||
}),
|
||||
hasClass () {
|
||||
if (!this.member) return false;
|
||||
return this.$store.getters['members:hasClass'](this.member);
|
||||
},
|
||||
isBuffed () {
|
||||
if (!this.member) return false;
|
||||
return this.$store.getters['members:isBuffed'](this.member);
|
||||
},
|
||||
paddingTop () {
|
||||
@@ -197,28 +200,30 @@ export default {
|
||||
let val = '24px';
|
||||
|
||||
if (!this.avatarOnly) {
|
||||
if (this.member.items.currentPet) val = '24px';
|
||||
if (this.member.items.currentMount) val = '0px';
|
||||
if (this.member?.items.currentPet) val = '24px';
|
||||
if (this.member?.items.currentMount) val = '0px';
|
||||
}
|
||||
|
||||
return val;
|
||||
},
|
||||
backgroundClass () {
|
||||
const { background } = this.member.preferences;
|
||||
if (this.member) {
|
||||
const { background } = this.member.preferences;
|
||||
|
||||
const allowToShowBackground = !this.avatarOnly || this.withBackground;
|
||||
const allowToShowBackground = !this.avatarOnly || this.withBackground;
|
||||
|
||||
if (this.overrideAvatarGear && this.overrideAvatarGear.background) {
|
||||
return `background_${this.overrideAvatarGear.background}`;
|
||||
if (this.overrideAvatarGear && this.overrideAvatarGear.background) {
|
||||
return `background_${this.overrideAvatarGear.background}`;
|
||||
}
|
||||
|
||||
if (background && allowToShowBackground) {
|
||||
return `background_${this.member.preferences.background}`;
|
||||
}
|
||||
}
|
||||
|
||||
if (background && allowToShowBackground) {
|
||||
return `background_${this.member.preferences.background}`;
|
||||
}
|
||||
|
||||
return '';
|
||||
},
|
||||
visualBuffs () {
|
||||
if (!this.member) return {};
|
||||
return {
|
||||
snowball: `avatar_snowball_${this.member.stats.class}`,
|
||||
spookySparkles: 'ghost',
|
||||
@@ -227,15 +232,16 @@ export default {
|
||||
};
|
||||
},
|
||||
skinClass () {
|
||||
if (!this.member) return '';
|
||||
const baseClass = `skin_${this.member.preferences.skin}`;
|
||||
|
||||
return `${baseClass}${this.member.preferences.sleep ? '_sleep' : ''}`;
|
||||
},
|
||||
costumeClass () {
|
||||
return this.member.preferences.costume ? 'costume' : 'equipped';
|
||||
return this.member?.preferences.costume ? 'costume' : 'equipped';
|
||||
},
|
||||
specialMountClass () {
|
||||
if (!this.avatarOnly && this.member.items.currentMount && this.member.items.currentMount.includes('Kangaroo')) {
|
||||
if (!this.avatarOnly && this.member?.items.currentMount && this.member?.items.currentMount.includes('Kangaroo')) {
|
||||
return 'offset-kangaroo';
|
||||
}
|
||||
|
||||
@@ -248,12 +254,13 @@ export default {
|
||||
)) {
|
||||
return this.foolPet(this.member.items.currentPet);
|
||||
}
|
||||
if (this.member.items.currentPet) return `Pet-${this.member.items.currentPet}`;
|
||||
if (this.member?.items.currentPet) return `Pet-${this.member.items.currentPet}`;
|
||||
return '';
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getGearClass (gearType) {
|
||||
if (!this.member) return '';
|
||||
let result = this.member.items.gear[this.costumeClass][gearType];
|
||||
|
||||
if (this.overrideAvatarGear && this.overrideAvatarGear[gearType]) {
|
||||
@@ -263,6 +270,7 @@ export default {
|
||||
return result;
|
||||
},
|
||||
hideGear (gearType) {
|
||||
if (!this.member) return true;
|
||||
if (gearType === 'weapon') {
|
||||
const equippedWeapon = this.member.items.gear[this.costumeClass][gearType];
|
||||
|
||||
@@ -288,6 +296,7 @@ export default {
|
||||
this.$root.$emit('castEnd', this.member, 'user', e);
|
||||
},
|
||||
showAvatar () {
|
||||
if (!this.member) return false;
|
||||
if (!this.showVisualBuffs) return true;
|
||||
|
||||
const { buffs } = this.member.stats;
|
||||
|
||||
@@ -8,23 +8,15 @@
|
||||
slot="modal-header"
|
||||
class="bug-report-modal-header"
|
||||
>
|
||||
<h2 v-once>
|
||||
{{ $t('reportBug') }}
|
||||
<h2>
|
||||
{{ question ? $t('askQuestion') : $t('reportBug') }}
|
||||
</h2>
|
||||
|
||||
<div
|
||||
v-once
|
||||
class="report-bug-header-describe"
|
||||
>
|
||||
{{ $t('reportBugHeaderDescribe') }}
|
||||
</div>
|
||||
|
||||
<div class="dialog-close">
|
||||
<close-icon
|
||||
:purple="true"
|
||||
@click="close()"
|
||||
/>
|
||||
<div class="report-bug-header-describe">
|
||||
{{ question ? $t('askQuestionHeaderDescribe') : $t('reportBugHeaderDescribe') }}
|
||||
</div>
|
||||
<close-x
|
||||
@close="close()"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<form
|
||||
@@ -40,11 +32,8 @@
|
||||
>
|
||||
{{ $t('email') }}
|
||||
</label>
|
||||
<div
|
||||
v-once
|
||||
class="mb-2 description-label"
|
||||
>
|
||||
{{ $t('reportEmailText') }}
|
||||
<div class="mb-2 description-label">
|
||||
{{ question ? $t('questionEmailText') : $t('reportEmailText') }}
|
||||
</div>
|
||||
<input
|
||||
id="emailInput"
|
||||
@@ -64,21 +53,18 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<label v-once>
|
||||
{{ $t('reportDescription') }}
|
||||
<label>
|
||||
{{ question ? $t('question') : $t('reportDescription') }}
|
||||
</label>
|
||||
<div
|
||||
v-once
|
||||
class="mb-2 description-label"
|
||||
>
|
||||
{{ $t('reportDescriptionText') }}
|
||||
<div class="mb-2 description-label">
|
||||
{{ question ? $t('questionDescriptionText') : $t('reportDescriptionText') }}
|
||||
</div>
|
||||
<textarea
|
||||
v-model="message"
|
||||
class="form-control"
|
||||
rows="5"
|
||||
:required="true"
|
||||
:placeholder="$t('reportDescriptionPlaceholder')"
|
||||
:placeholder="question ? $t('questionPlaceholder') : $t('reportDescriptionPlaceholder')"
|
||||
:class="{'input-invalid': messageInvalid && this.message.length === 0}"
|
||||
>
|
||||
|
||||
@@ -89,7 +75,7 @@
|
||||
type="submit"
|
||||
:disabled="!message || !emailValid"
|
||||
>
|
||||
{{ $t('submitBugReport') }}
|
||||
{{ question ? $t('submitQuestion') : $t('submitBugReport') }}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
@@ -141,7 +127,7 @@ h2 {
|
||||
.bug-report-modal-header {
|
||||
color: $white;
|
||||
width: 100%;
|
||||
padding: 2rem 3rem 1.5rem 1.5rem;
|
||||
padding: 1.5rem 3rem 1.5rem 1.5rem;
|
||||
|
||||
background-image: linear-gradient(288deg, #{$purple-200}, #{$purple-300});
|
||||
}
|
||||
@@ -182,13 +168,13 @@ label {
|
||||
<script>
|
||||
import axios from 'axios';
|
||||
import isEmail from 'validator/lib/isEmail';
|
||||
import closeIcon from '@/components/shared/closeIcon';
|
||||
import closeX from '@/components/ui/closeX';
|
||||
import { mapState } from '@/libs/store';
|
||||
import { MODALS } from '@/libs/consts';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
closeIcon,
|
||||
closeX,
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
@@ -211,6 +197,7 @@ export default {
|
||||
await axios.post('/api/v4/bug-report', {
|
||||
message: this.message,
|
||||
email: this.email,
|
||||
question: this.question,
|
||||
});
|
||||
|
||||
this.message = '';
|
||||
@@ -233,6 +220,9 @@ export default {
|
||||
if (this.email.length <= 3) return false;
|
||||
return !this.emailValid;
|
||||
},
|
||||
question () {
|
||||
return this.$store.state.bugReportOptions.question;
|
||||
},
|
||||
},
|
||||
mounted () {
|
||||
const { user } = this;
|
||||
|
||||
@@ -418,6 +418,9 @@ export default {
|
||||
methods: {
|
||||
async shown () {
|
||||
this.groups = await this.$store.dispatch('guilds:getMyGuilds');
|
||||
this.groups = this.groups.filter(group => !(
|
||||
group.leaderOnly.challenges && group.leader !== this.user._id
|
||||
));
|
||||
|
||||
if (this.user.party && this.user.party._id) {
|
||||
await this.$store.dispatch('party:getParty');
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div>
|
||||
<challenge-modal
|
||||
:group-id="groupId"
|
||||
:group-id="group._id"
|
||||
@createChallenge="challengeCreated"
|
||||
/>
|
||||
<div
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
v-if="user._id !== msg.uuid && msg.uuid !== 'system'"
|
||||
class="avatar-left"
|
||||
:class="{ invisible: avatarUnavailable(msg) }"
|
||||
:member="msg.userStyles || cachedProfileData[msg.uuid]"
|
||||
:member="msg.userStyles || cachedProfileData[msg.uuid] || {}"
|
||||
:avatar-only="true"
|
||||
:hide-class-badge="true"
|
||||
:override-top-padding="'14px'"
|
||||
@@ -58,7 +58,7 @@
|
||||
<avatar
|
||||
v-if="user._id === msg.uuid"
|
||||
:class="{ invisible: avatarUnavailable(msg) }"
|
||||
:member="msg.userStyles || cachedProfileData[msg.uuid]"
|
||||
:member="msg.userStyles || cachedProfileData[msg.uuid] || {}"
|
||||
:avatar-only="true"
|
||||
:hide-class-badge="true"
|
||||
:override-top-padding="'14px'"
|
||||
|
||||
@@ -50,7 +50,6 @@ import notificationsMixin from '@/mixins/notifications';
|
||||
import Task from '@/components/tasks/task';
|
||||
|
||||
import taskDefaults from '@/../../common/script/libs/taskDefaults';
|
||||
import { TAVERN_ID } from '@/../../common/script/constants';
|
||||
|
||||
const baseUrl = 'https://habitica.com';
|
||||
|
||||
@@ -89,9 +88,7 @@ export default {
|
||||
createTask: 'tasks:create',
|
||||
}),
|
||||
groupPath () {
|
||||
if (this.groupId === TAVERN_ID) {
|
||||
return `${baseUrl}/groups/tavern`;
|
||||
} if (this.groupType === 'party') {
|
||||
if (this.groupType === 'party') {
|
||||
return `${baseUrl}/party`;
|
||||
}
|
||||
return `${baseUrl}/groups/guild/${this.groupId}`;
|
||||
|
||||
@@ -56,10 +56,7 @@ export default {
|
||||
return (this.$route.fullPath.indexOf('/faq')) !== -1;
|
||||
},
|
||||
showChatWarning () {
|
||||
if (this.$route.fullPath.indexOf('/groups') !== -1) return true;
|
||||
if (this.$route.fullPath.indexOf('/tavern-and-guilds') !== -1) return false;
|
||||
if (this.$route.fullPath.indexOf('/tavern') !== -1) return true;
|
||||
return this.faqPage;
|
||||
return false;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -194,48 +194,6 @@
|
||||
>
|
||||
{{ $t('party') }}
|
||||
</b-nav-item>
|
||||
<li
|
||||
class="topbar-item droppable"
|
||||
:class="{
|
||||
'active': $route.path.startsWith('/groups')}"
|
||||
>
|
||||
<div
|
||||
class="chevron rotate"
|
||||
@click="dropdownMobile($event)"
|
||||
>
|
||||
<div
|
||||
v-once
|
||||
class="chevron-icon-down"
|
||||
v-html="icons.chevronDown"
|
||||
></div>
|
||||
</div>
|
||||
<router-link
|
||||
class="nav-link"
|
||||
:to="{name: 'tavern'}"
|
||||
>
|
||||
{{ $t('guilds') }}
|
||||
</router-link>
|
||||
<div class="topbar-dropdown">
|
||||
<router-link
|
||||
class="topbar-dropdown-item dropdown-item"
|
||||
:to="{name: 'tavern'}"
|
||||
>
|
||||
{{ $t('tavern') }}
|
||||
</router-link>
|
||||
<router-link
|
||||
class="topbar-dropdown-item dropdown-item"
|
||||
:to="{name: 'myGuilds'}"
|
||||
>
|
||||
{{ $t('myGuilds') }}
|
||||
</router-link>
|
||||
<router-link
|
||||
class="topbar-dropdown-item dropdown-item"
|
||||
:to="{name: 'guildsDiscovery'}"
|
||||
>
|
||||
{{ $t('guildsDiscovery') }}
|
||||
</router-link>
|
||||
</div>
|
||||
</li>
|
||||
<li
|
||||
class="topbar-item droppable"
|
||||
:class="{
|
||||
@@ -354,22 +312,18 @@
|
||||
>
|
||||
{{ $t('reportBug') }}
|
||||
</a>
|
||||
<router-link
|
||||
<a
|
||||
class="topbar-dropdown-item dropdown-item"
|
||||
to="/groups/guild/5481ccf3-5d2d-48a9-a871-70a7380cee5a"
|
||||
target="_blank"
|
||||
@click.prevent="openBugReportModal(true)"
|
||||
>
|
||||
{{ $t('askQuestion') }}
|
||||
</router-link>
|
||||
</a>
|
||||
<a
|
||||
class="topbar-dropdown-item dropdown-item"
|
||||
href="https://docs.google.com/forms/d/e/1FAIpQLScPhrwq_7P1C6PTrI3lbvTsvqGyTNnGzp1ugi1Ml0PFee_p5g/viewform?usp=sf_link"
|
||||
target="_blank"
|
||||
>{{ $t('requestFeature') }}</a>
|
||||
<a
|
||||
class="topbar-dropdown-item dropdown-item"
|
||||
href="https://habitica.fandom.com/wiki/Contributing_to_Habitica"
|
||||
target="_blank"
|
||||
>{{ $t('contributing') }}</a>
|
||||
<a
|
||||
class="topbar-dropdown-item dropdown-item"
|
||||
href="https://habitica.fandom.com/wiki/Habitica_Wiki"
|
||||
@@ -661,6 +615,7 @@ body.modal-open #habitica-menu {
|
||||
|
||||
&:hover {
|
||||
background: $purple-300;
|
||||
text-decoration: none;
|
||||
|
||||
&:last-child {
|
||||
border-bottom-right-radius: 5px;
|
||||
|
||||
@@ -65,7 +65,7 @@ export default {
|
||||
}
|
||||
|
||||
await this.$store.dispatch('guilds:join', { groupId: group.id, type: 'guild' });
|
||||
this.$router.push({ name: 'guild', params: { groupId: group.id } });
|
||||
this.$router.push({ name: 'groupPlanDetailTaskInformation', params: { groupId: group.id } });
|
||||
},
|
||||
reject () {
|
||||
this.$store.dispatch('guilds:rejectInvite', { groupId: this.notification.data.id, type: 'guild' });
|
||||
|
||||
@@ -270,6 +270,9 @@ export default {
|
||||
methods: {
|
||||
percent,
|
||||
showMemberModal (member) {
|
||||
if (this.$route.name === 'userProfile' && this.$route.params?.userId === member._id) {
|
||||
return;
|
||||
}
|
||||
this.$router.push({ name: 'userProfile', params: { userId: member._id } });
|
||||
},
|
||||
},
|
||||
|
||||
@@ -11,8 +11,6 @@
|
||||
<low-health />
|
||||
<level-up />
|
||||
<choose-class />
|
||||
<testing />
|
||||
<testingletiant />
|
||||
<rebirth-enabled />
|
||||
<contributor />
|
||||
<won-challenge />
|
||||
@@ -127,8 +125,6 @@ import chooseClass from './achievements/chooseClass';
|
||||
import armoireEmpty from './achievements/armoireEmpty';
|
||||
import questCompleted from './achievements/questCompleted';
|
||||
import questInvitation from './achievements/questInvitation';
|
||||
import testing from './achievements/testing';
|
||||
import testingletiant from './achievements/testingletiant';
|
||||
import rebirthEnabled from './achievements/rebirthEnabled';
|
||||
import contributor from './achievements/contributor';
|
||||
import invitedFriend from './achievements/invitedFriend';
|
||||
@@ -269,8 +265,6 @@ export default {
|
||||
armoireEmpty,
|
||||
questCompleted,
|
||||
questInvitation,
|
||||
testing,
|
||||
testingletiant,
|
||||
rebirthEnabled,
|
||||
contributor,
|
||||
loginIncentives,
|
||||
@@ -300,7 +294,6 @@ export default {
|
||||
// general notifications
|
||||
'CRON',
|
||||
'FIRST_DROPS',
|
||||
'GUILD_PROMPT',
|
||||
'LOGIN_INCENTIVE',
|
||||
'NEW_CONTRIBUTOR_LEVEL',
|
||||
'ONBOARDING_COMPLETE',
|
||||
@@ -705,14 +698,6 @@ export default {
|
||||
this.$root.$emit('bv::show::modal', 'first-drops');
|
||||
}
|
||||
break;
|
||||
case 'GUILD_PROMPT':
|
||||
// @TODO: I'm pretty sure we can find better names for these
|
||||
if (notification.data.textletiant === -1) {
|
||||
this.$root.$emit('bv::show::modal', 'testing');
|
||||
} else {
|
||||
this.$root.$emit('bv::show::modal', 'testingletiant');
|
||||
}
|
||||
break;
|
||||
case 'REBIRTH_ENABLED':
|
||||
this.$root.$emit('bv::show::modal', 'rebirth-enabled');
|
||||
break;
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
<template>
|
||||
<div class="container-fluid text-center">
|
||||
<div class="row">
|
||||
<div class="col-md-6 offset-3">
|
||||
<h1>{{ $t('checkOutMobileApps') }}</h1>
|
||||
<div
|
||||
class="promo_habitica"
|
||||
style="border-radius:25px;margin:auto;margin-bottom:30px"
|
||||
></div>
|
||||
<a
|
||||
href="https://play.google.com/store/apps/details?id=com.habitrpg.android.habitica&utm_source=global_co&utm_medium=prtnr&utm_content=Mar2515&utm_campaign=PartBadge&pcampaignid=MKT-AC-global-none-all-co-pr-py-PartBadges-Oct1515-1&utm_source=global_co&utm_medium=prtnr&utm_content=Mar2515&utm_campaign=PartBadge&pcampaignid=MKT-AC-global-none-all-co-pr-py-PartBadges-Oct1515-1"
|
||||
>
|
||||
<img
|
||||
alt="Get it on Google Play"
|
||||
src="https://play.google.com/intl/en_us/badges/images/apps/en-play-badge.png"
|
||||
style="width:139px;height:45px;image-rendering:auto;vertical-align:top"
|
||||
>
|
||||
</a>
|
||||
<a
|
||||
href="https://geo.itunes.apple.com/us/app/habitica/id994882113?mt=8"
|
||||
style="display:inline-block;overflow:hidden;background:url(https://linkmaker.itunes.apple.com/images/badges/en-us/badge_appstore-lrg.svg#svgView) no-repeat;background-size:100%;width:152px;height:45px;margin-left:20px;image-rendering:auto"
|
||||
></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,99 +1,61 @@
|
||||
<template>
|
||||
<div class="container-fluid">
|
||||
<div class="container-fluid w-75 mx-auto">
|
||||
<h1>{{ $t('communityGuidelines') }}</h1>
|
||||
<hr>
|
||||
<p>{{ $t('lastUpdated') }} February 8, 2023</p>
|
||||
<p>{{ $t('lastUpdated') }} June 8, 2023</p>
|
||||
<h2 id="welcome">
|
||||
{{ $t('commGuideHeadingWelcome') }}
|
||||
</h2>
|
||||
<div class="media align-items-center">
|
||||
<img src="~@/assets/images/community-guidelines/intro.png">
|
||||
<div class="media-body">
|
||||
<p v-html="$t('commGuidePara001')"></p>
|
||||
<p v-html="$t('commGuidePara002')"></p>
|
||||
</div>
|
||||
</div>
|
||||
<img
|
||||
src="~@/assets/images/community-guidelines/intro.png"
|
||||
class="mb-3"
|
||||
>
|
||||
<p v-html="$t('commGuidePara001')"></p>
|
||||
<p v-html="$t('commGuidePara002')"></p>
|
||||
<p v-html="$t('commGuidePara003')"></p>
|
||||
<h2 id="interactions">
|
||||
{{ $t('commGuideHeadingInteractions') }}
|
||||
</h2>
|
||||
<p v-html="$t('commGuidePara015')"></p>
|
||||
<p v-html="$t('commGuidePara016')"></p>
|
||||
<p v-html="$t('commGuidePara017')"></p>
|
||||
<ul>
|
||||
<li v-html="$t('commGuideList01F')"></li>
|
||||
<li v-html="$t('commGuideList01A')"></li>
|
||||
<li v-html="$t('commGuideList01B')"></li>
|
||||
<li v-html="$t('commGuideList01C')"></li>
|
||||
<li v-html="$t('commGuideList01D')"></li>
|
||||
<li v-html="$t('commGuideList01E')"></li>
|
||||
<li v-html="$t('commGuideList01F')"></li>
|
||||
</ul>
|
||||
<div class="media align-items-center">
|
||||
<img src="~@/assets/images/community-guidelines/publicSpaces.png">
|
||||
</div>
|
||||
<ul>
|
||||
<li v-html="$t('commGuideList02N')"></li>
|
||||
<li v-html="$t('commGuideList02A')"></li>
|
||||
<li v-html="$t('commGuideList02B')"></li>
|
||||
<li v-html="$t('commGuideList02G')"></li>
|
||||
<li><strong>{{ $t('commGuideList01A') }}</strong></li>
|
||||
<li v-html="$t('commGuideList02C')"></li>
|
||||
<li v-html="$t('commGuideList02N')"></li>
|
||||
<li v-html="$t('commGuideList02H')"></li>
|
||||
<li v-html="$t('commGuideList02A')"></li>
|
||||
<li v-html="$t('commGuideList02I')"></li>
|
||||
<li v-html="$t('commGuideList02G')"></li>
|
||||
<li v-html="$t('commGuideList02D')"></li>
|
||||
<li v-html="$t('commGuideList02E')"></li>
|
||||
<li v-html="$t('commGuideList02F')"></li>
|
||||
<li v-html="$t('commGuideList02O')"></li>
|
||||
<li v-html="$t('commGuidePara037')"></li>
|
||||
<li v-html="$t('commGuideList02P')"></li>
|
||||
<li v-html="$t('commGuideList02Q')"></li>
|
||||
<li v-html="$t('commGuideList02M')"></li>
|
||||
<li v-html="$t('commGuideList02L')"></li>
|
||||
<li v-html="$t('commGuideList02J')"></li>
|
||||
<li v-html="$t('commGuideList02K')"></li>
|
||||
<li v-html="$t('commGuideList02L')"></li>
|
||||
</ul>
|
||||
<p v-html="$t('commGuidePara019')"></p>
|
||||
<p v-html="$t('commGuidePara020')"></p>
|
||||
<p v-html="$t('commGuidePara020A')"></p>
|
||||
<p v-html="$t('commGuidePara021')"></p>
|
||||
<h3 id="tavern">
|
||||
{{ $t('commGuideHeadingTavern') }}
|
||||
</h3>
|
||||
<div class="media align-items-center">
|
||||
<img src="~@/assets/images/community-guidelines/tavern.png">
|
||||
<div class="media-body">
|
||||
<p v-html="$t('commGuidePara022')"></p>
|
||||
<p v-html="$t('commGuidePara023')"></p>
|
||||
<p v-html="$t('commGuidePara024')"></p>
|
||||
</div>
|
||||
</div>
|
||||
<h3 id="guilds">
|
||||
{{ $t('commGuideHeadingPublicGuilds') }}
|
||||
</h3>
|
||||
<div class="media align-items-center">
|
||||
<div class="media-body">
|
||||
<p v-html="$t('commGuidePara029')"></p>
|
||||
<p v-html="$t('commGuidePara031')"></p>
|
||||
</div>
|
||||
<img src="~@/assets/images/community-guidelines/publicGuilds.png">
|
||||
</div>
|
||||
<p v-html="$t('commGuidePara033')"></p>
|
||||
<p v-html="$t('commGuidePara035')"></p>
|
||||
<p v-html="$t('commGuidePara036')"></p>
|
||||
<p v-html="$t('commGuidePara037')"></p>
|
||||
<p v-html="$t('commGuidePara038')"></p>
|
||||
<h2 id="infractions-consequences-restoration">
|
||||
{{ $t('commGuideHeadingInfractionsEtc') }}
|
||||
</h2>
|
||||
<h3 id="infractions">
|
||||
{{ $t('commGuideHeadingInfractions') }}
|
||||
</h3>
|
||||
<div class="media align-items-center">
|
||||
<img src="~@/assets/images/community-guidelines/infractions.png">
|
||||
<div class="media-body">
|
||||
<p v-html="$t('commGuidePara050')"></p>
|
||||
<p v-html="$t('commGuidePara051')"></p>
|
||||
</div>
|
||||
</div>
|
||||
<img
|
||||
src="~@/assets/images/community-guidelines/infractions.png"
|
||||
class="mb-3"
|
||||
>
|
||||
<p v-html="$t('commGuidePara050')"></p>
|
||||
<p v-html="$t('commGuidePara051')"></p>
|
||||
<h4>{{ $t('commGuideHeadingSevereInfractions') }}</h4>
|
||||
<p v-html="$t('commGuidePara052')"></p>
|
||||
<p v-html="$t('commGuidePara053')"></p>
|
||||
<ul>
|
||||
<li v-html="$t('commGuideList05A')"></li>
|
||||
<li v-html="$t('commGuideList05B')"></li>
|
||||
<li v-html="$t('commGuideList05C')"></li>
|
||||
<li v-html="$t('commGuideList05D')"></li>
|
||||
@@ -101,59 +63,34 @@
|
||||
<li v-html="$t('commGuideList05F')"></li>
|
||||
<li v-html="$t('commGuideList05G')"></li>
|
||||
<li v-html="$t('commGuideList05H')"></li>
|
||||
<li v-html="$t('commGuideList05A')"></li>
|
||||
</ul>
|
||||
<h4>{{ $t('commGuideHeadingModerateInfractions') }}</h4>
|
||||
<p v-html="$t('commGuidePara054')"></p>
|
||||
<p v-html="$t('commGuidePara055')"></p>
|
||||
<ul>
|
||||
<li v-html="$t('commGuideList06A')"></li>
|
||||
<li v-html="$t('commGuideList06B')"></li>
|
||||
<li v-html="$t('commGuideList06C')"></li>
|
||||
<li v-html="$t('commGuideList06D')"></li>
|
||||
<li v-html="$t('commGuideList06E')"></li>
|
||||
</ul>
|
||||
<h4>{{ $t('commGuideHeadingMinorInfractions') }}</h4>
|
||||
<p v-html="$t('commGuidePara056')"></p>
|
||||
<p v-html="$t('commGuidePara057')"></p>
|
||||
<ul>
|
||||
<li v-html="$t('commGuideList07A')"></li>
|
||||
<li v-html="$t('commGuideList07B')"></li>
|
||||
</ul>
|
||||
<p v-html="$t('commGuidePara057A')"></p>
|
||||
<h3 id="consequences">
|
||||
{{ $t('commGuideHeadingConsequences') }}
|
||||
</h3>
|
||||
<div class="media align-items-center">
|
||||
<div class="media-body">
|
||||
<p v-html="$t('commGuidePara058')"></p>
|
||||
<p v-html="$t('commGuidePara059')"></p>
|
||||
</div>
|
||||
<img src="~@/assets/images/community-guidelines/consequences.png">
|
||||
</div>
|
||||
<p v-html="$t('commGuidePara060')"></p>
|
||||
<ul>
|
||||
<li v-html="$t('commGuideList08A')"></li>
|
||||
<li v-html="$t('commGuideList08B')"></li>
|
||||
<li v-html="$t('commGuideList08C')"></li>
|
||||
</ul>
|
||||
<p v-html="$t('commGuidePara060A')"></p>
|
||||
<p v-html="$t('commGuidePara060B')"></p>
|
||||
<p v-html="$t('commGuidePara059')"></p>
|
||||
<img src="~@/assets/images/community-guidelines/consequences.png">
|
||||
<h4>{{ $t('commGuideHeadingSevereConsequences') }}</h4>
|
||||
<ul>
|
||||
<li v-html="$t('commGuideList09A')"></li>
|
||||
<li v-html="$t('commGuideList09C')"></li>
|
||||
<li v-html="$t('commGuideList09D')"></li>
|
||||
<li v-html="$t('commGuideList09E')"></li>
|
||||
</ul>
|
||||
<h4>{{ $t('commGuideHeadingModerateConsequences') }}</h4>
|
||||
<ul>
|
||||
<li>
|
||||
{{ $t('commGuideList10A') }}
|
||||
<ul>
|
||||
<li v-html="$t('commGuideList10A1')"></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li v-html="$t('commGuideList10D')"></li>
|
||||
<li v-html="$t('commGuideList10F')"></li>
|
||||
<li v-html="$t('commGuideList10G')"></li>
|
||||
</ul>
|
||||
<h4>{{ $t('commGuideHeadingMinorConsequences') }}</h4>
|
||||
<ul>
|
||||
@@ -166,56 +103,53 @@
|
||||
<h3 id="restoration">
|
||||
{{ $t('commGuideHeadingRestoration') }}
|
||||
</h3>
|
||||
<div class="media align-items-center">
|
||||
<img src="~@/assets/images/community-guidelines/restoration.png">
|
||||
<div class="media-body">
|
||||
<p v-html="$t('commGuidePara061')"></p>
|
||||
<p v-html="$t('commGuidePara062')"></p>
|
||||
</div>
|
||||
</div>
|
||||
<img
|
||||
src="~@/assets/images/community-guidelines/restoration.png"
|
||||
class="mb-3"
|
||||
>
|
||||
<p v-html="$t('commGuidePara061')"></p>
|
||||
<p v-html="$t('commGuidePara063')"></p>
|
||||
<h2 id="meet-the-mods">
|
||||
{{ $t('commGuideHeadingMeet') }}
|
||||
</h2>
|
||||
<p v-html="$t('commGuidePara007')"></p>
|
||||
<p v-html="$t('commGuidePara009')"></p>
|
||||
<div class="media align-items-center">
|
||||
<img src="~@/assets/images/community-guidelines/staff.png">
|
||||
<div class="media-body">
|
||||
<ul>
|
||||
<li>
|
||||
{{ $t('commGuideAKA', {habitName: 'heyeilatan', realName: 'Natalie'}) }}
|
||||
({{ $t('commGuideOnGitHub', {gitHubName: 'CuriousMagpie'}) }})
|
||||
- Web Developer
|
||||
</li>
|
||||
<li>
|
||||
{{ $t('commGuideAKA', {habitName: 'Viirus', realName: 'Phillip'}) }}
|
||||
- Mobile Developer
|
||||
</li>
|
||||
<li>
|
||||
{{ $t('commGuideAKA', {habitName: 'redphoenix', realName: 'Vicky'}) }}
|
||||
({{ $t('commGuideOnGitHub', {gitHubName: 'veeeeeee'}) }})
|
||||
- Co-Founder
|
||||
</li>
|
||||
<li>
|
||||
{{ $t('commGuideAKA', {habitName: 'Beffymaroo', realName: 'Beth'}) }}
|
||||
- Art, Community Management, Many Hats
|
||||
</li>
|
||||
<li>
|
||||
{{ $t('commGuideAKA', {habitName: 'SabreCat', realName: 'Sabe'}) }}
|
||||
- Web Developer
|
||||
</li>
|
||||
<li>
|
||||
{{ $t('commGuideAKA', {habitName: 'Apollo', realName: 'Tressley'}) }}
|
||||
- Designer
|
||||
</li>
|
||||
<li>
|
||||
{{ $t('commGuideAKA', {habitName: 'Piyo', realName: 'Sara'}) }}
|
||||
- Mobile Designer
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<img
|
||||
src="~@/assets/images/community-guidelines/staff.png"
|
||||
class="mb-3"
|
||||
>
|
||||
<ul>
|
||||
<li>
|
||||
{{ $t('commGuideAKA', {habitName: 'heyeilatan', realName: 'Natalie'}) }}
|
||||
({{ $t('commGuideOnGitHub', {gitHubName: 'CuriousMagpie'}) }})
|
||||
- Web Developer
|
||||
</li>
|
||||
<li>
|
||||
{{ $t('commGuideAKA', {habitName: 'redphoenix', realName: 'Vicky'}) }}
|
||||
({{ $t('commGuideOnGitHub', {gitHubName: 'veeeeeee'}) }})
|
||||
- Co-Founder
|
||||
</li>
|
||||
<li>
|
||||
{{ $t('commGuideAKA', {habitName: 'Beffymaroo', realName: 'Beth'}) }}
|
||||
- Art, Community Management, Many Hats
|
||||
</li>
|
||||
<li>
|
||||
{{ $t('commGuideAKA', {habitName: 'SabreCat', realName: 'Sabe'}) }}
|
||||
- Web Developer
|
||||
</li>
|
||||
<li>
|
||||
{{ $t('commGuideAKA', {habitName: 'Apollo', realName: 'Tressley'}) }}
|
||||
- Designer
|
||||
</li>
|
||||
<li>
|
||||
{{ $t('commGuideAKA', {habitName: 'Piyo', realName: 'Sara'}) }}
|
||||
- Mobile Designer
|
||||
</li>
|
||||
<li>
|
||||
{{ $t('commGuideAKA', {habitName: 'Viirus', realName: 'Phillip'}) }}
|
||||
- Mobile Developer
|
||||
</li>
|
||||
</ul>
|
||||
<p v-html="$t('commGuidePara013')"></p>
|
||||
<p>
|
||||
{{ $t('commGuidePara014') }}<br>
|
||||
@@ -223,7 +157,6 @@
|
||||
Lemoness, lefnire, Slappybag, litenull, Shaner, Bobbyroberts99, wc8,
|
||||
Breadstrings, Megan, Blade, Daniel the Bard, deilann, shanaqui, Nakonana,
|
||||
Dewines, Alys, Fox_town, MaybeSteveRogers, and Cantras.
|
||||
|
||||
</em>
|
||||
</p>
|
||||
<h2 id="final">
|
||||
@@ -235,16 +168,12 @@
|
||||
{{ $t('commGuideHeadingLinks') }}
|
||||
</h2>
|
||||
<ul>
|
||||
<li v-html="$t('commGuideLink01')"></li>
|
||||
<li v-html="$t('commGuideLink02')"></li>
|
||||
<li><a href="/static/faq">{{ $t('faq') }}</a></li>
|
||||
<li v-html="$t('commGuideLink03')"></li>
|
||||
<li v-html="$t('commGuideLink04')"></li>
|
||||
<li v-html="$t('commGuideLink06')"></li>
|
||||
<li v-html="$t('commGuideLink07')"></li>
|
||||
</ul>
|
||||
<p v-html="$t('commGuidePara069')"></p>
|
||||
<ul class="list-2col list-unstyled">
|
||||
<li>Beffymaroo</li>
|
||||
<ul>
|
||||
<li>Breadstrings</li>
|
||||
<li>Draayder</li>
|
||||
<li>Kiwibot</li>
|
||||
@@ -258,12 +187,3 @@
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
.list-2col {
|
||||
width: 50%;
|
||||
columns: 2;
|
||||
-moz-columns: 2;
|
||||
-webkit-columns: 2;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -35,8 +35,8 @@
|
||||
:
|
||||
<a
|
||||
target="_blank"
|
||||
href="/groups/guild/5481ccf3-5d2d-48a9-a871-70a7380cee5a"
|
||||
>Habitica Help guild</a>
|
||||
@click.prevent="openBugReportModal(true)"
|
||||
> {{ $t('askQuestion') }}</a>
|
||||
<br>
|
||||
{{ $t('businessInquiries') }}
|
||||
:
|
||||
|
||||
@@ -31,7 +31,6 @@
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<img src="~@/assets/images/marketing/guild.png">
|
||||
<h2>{{ $t('marketing2Lead1Title') }}</h2>
|
||||
<p>{{ $t('marketing2Lead1') }}</p>
|
||||
<img src="~@/assets/images/marketing/vice3.png">
|
||||
|
||||
@@ -12,12 +12,15 @@
|
||||
<p v-markdown="$t(`webStep${step}Text`, stepVars[step])"></p>
|
||||
<hr>
|
||||
</div>
|
||||
<p
|
||||
v-markdown="$t('overviewQuestions', {
|
||||
faqUrl: '/static/faq/',
|
||||
helpGuildUrl: '/groups/guild/5481ccf3-5d2d-48a9-a871-70a7380cee5a'
|
||||
})"
|
||||
></p>
|
||||
<p>
|
||||
<span v-html="$t('overviewQuestionsRevised')"></span>
|
||||
<a
|
||||
target="_blank"
|
||||
@click.prevent="openBugReportModal(true)"
|
||||
>
|
||||
{{ $t('askQuestion') }}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -35,11 +38,13 @@
|
||||
|
||||
<script>
|
||||
import markdownDirective from '@/directives/markdown';
|
||||
import reportBug from '@/mixins/reportBug.js';
|
||||
|
||||
export default {
|
||||
directives: {
|
||||
markdown: markdownDirective,
|
||||
},
|
||||
mixins: [reportBug],
|
||||
data () {
|
||||
return {
|
||||
stepsNum: ['1', '2', '3'],
|
||||
|
||||
@@ -12,7 +12,10 @@
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
|
||||
.modal-close {
|
||||
color: $black;
|
||||
position: absolute;
|
||||
right: 16px;
|
||||
top: 16px;
|
||||
@@ -22,10 +25,10 @@
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
vertical-align: middle;
|
||||
opacity: 0.75;
|
||||
opacity: 0.5;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
opacity: 0.75;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,15 +53,6 @@ export default {
|
||||
hideNavigation: true,
|
||||
},
|
||||
]],
|
||||
tavern: [[
|
||||
{
|
||||
orphan: true,
|
||||
intro: this.$t('tourTavernPage'),
|
||||
final: true,
|
||||
proceed: this.$t('awesome'),
|
||||
hideNavigation: true,
|
||||
},
|
||||
]],
|
||||
party: [[
|
||||
{
|
||||
orphan: true,
|
||||
@@ -71,11 +62,6 @@ export default {
|
||||
hideNavigation: true,
|
||||
},
|
||||
]],
|
||||
guilds: [[
|
||||
{
|
||||
intro: this.$t('tourGuildsPage'),
|
||||
},
|
||||
]],
|
||||
challenges: [[
|
||||
{
|
||||
intro: this.$t('tourChallengesPage'),
|
||||
@@ -129,9 +115,7 @@ export default {
|
||||
switch (this.$route.name) { // eslint-disable-line default-case
|
||||
// case 'options.profile.avatar': return goto('intro', 5);
|
||||
case 'stats': return this.goto('stats', 0);
|
||||
case 'tavern': return this.goto('tavern', 0);
|
||||
case 'party': return this.goto('party', 0);
|
||||
case 'guildsDiscovery': return this.goto('guilds', 0);
|
||||
case 'challenges': return this.goto('challenges', 0);
|
||||
case 'patrons': return this.goto('hall', 0);
|
||||
case 'items': return this.goto('market', 0);
|
||||
|
||||
@@ -2,7 +2,8 @@ import { MODALS } from '@/libs/consts';
|
||||
|
||||
export default {
|
||||
methods: {
|
||||
openBugReportModal () {
|
||||
openBugReportModal (questionMode = false) {
|
||||
this.$store.state.bugReportOptions.question = questionMode;
|
||||
this.$root.$emit('bv::show::modal', MODALS.BUG_REPORT);
|
||||
},
|
||||
},
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import { NotFoundPage } from './shared-route-imports';
|
||||
|
||||
export const DEPRECATED_ROUTES = {
|
||||
path: '/groups',
|
||||
component: NotFoundPage,
|
||||
children: [
|
||||
{ name: 'tavern', path: 'tavern' },
|
||||
{
|
||||
name: 'myGuilds',
|
||||
path: 'myGuilds',
|
||||
},
|
||||
{
|
||||
name: 'guildsDiscovery',
|
||||
path: 'discovery',
|
||||
},
|
||||
{
|
||||
name: 'guild',
|
||||
path: 'guild/:groupId',
|
||||
props: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -4,52 +4,17 @@ import * as Analytics from '@/libs/analytics';
|
||||
import getStore from '@/store';
|
||||
import handleRedirect from './handleRedirect';
|
||||
|
||||
import ParentPage from '@/components/parentPage';
|
||||
import { PAGES } from '@/libs/consts';
|
||||
import { STATIC_ROUTES } from './static-routes';
|
||||
import { USER_ROUTES } from './user-routes';
|
||||
import { DEPRECATED_ROUTES } from '@/router/deprecated-routes';
|
||||
import { ProfilePage } from './shared-route-imports';
|
||||
|
||||
// NOTE: when adding a page make sure to implement the `common:setTitle` action
|
||||
|
||||
// Static Pages
|
||||
const StaticWrapper = () => import(/* webpackChunkName: "entry" */'@/components/static/staticWrapper');
|
||||
const HomePage = () => import(/* webpackChunkName: "entry" */'@/components/static/home');
|
||||
|
||||
const AppPage = () => import(/* webpackChunkName: "static" */'@/components/static/app');
|
||||
const AppleRedirectPage = () => import(/* webpackChunkName: "static" */'@/components/static/appleRedirect');
|
||||
const ClearBrowserDataPage = () => import(/* webpackChunkName: "static" */'@/components/static/clearBrowserData');
|
||||
const CommunityGuidelinesPage = () => import(/* webpackChunkName: "static" */'@/components/static/communityGuidelines');
|
||||
const ContactPage = () => import(/* webpackChunkName: "static" */'@/components/static/contact');
|
||||
const FAQPage = () => import(/* webpackChunkName: "static" */'@/components/static/faq');
|
||||
const FeaturesPage = () => import(/* webpackChunkName: "static" */'@/components/static/features');
|
||||
const GroupPlansPage = () => import(/* webpackChunkName: "static" */'@/components/static/groupPlans');
|
||||
// Commenting out merch page see
|
||||
// https://github.com/HabitRPG/habitica/issues/12039
|
||||
// const MerchPage = () => import(/* webpackChunkName: "static" */'@/components/static/merch');
|
||||
const NewsPage = () => import(/* webpackChunkName: "static" */'@/components/static/newStuff');
|
||||
const OverviewPage = () => import(/* webpackChunkName: "static" */'@/components/static/overview');
|
||||
const PressKitPage = () => import(/* webpackChunkName: "static" */'@/components/static/pressKit');
|
||||
const PrivacyPage = () => import(/* webpackChunkName: "static" */'@/components/static/privacy');
|
||||
const ChatSunsetFaq = () => import(/* webpackChunkName: "static" */'@/components/static/chatSunsetFaq');
|
||||
const TermsPage = () => import(/* webpackChunkName: "static" */'@/components/static/terms');
|
||||
|
||||
const RegisterLoginReset = () => import(/* webpackChunkName: "auth" */'@/components/auth/registerLoginReset');
|
||||
const Logout = () => import(/* webpackChunkName: "auth" */'@/components/auth/logout');
|
||||
|
||||
// User Pages
|
||||
// const StatsPage = () => import(/* webpackChunkName: "user" */'./components/userMenu/stats');
|
||||
// const AchievementsPage =
|
||||
// () => import(/* webpackChunkName: "user" */'./components/userMenu/achievements');
|
||||
const ProfilePage = () => import(/* webpackChunkName: "user" */'@/components/userMenu/profilePage');
|
||||
|
||||
// Settings
|
||||
const Settings = () => import(/* webpackChunkName: "settings" */'@/components/settings/index');
|
||||
const API = () => import(/* webpackChunkName: "settings" */'@/components/settings/api');
|
||||
const DataExport = () => import(/* webpackChunkName: "settings" */'@/components/settings/dataExport');
|
||||
const Notifications = () => import(/* webpackChunkName: "settings" */'@/components/settings/notifications');
|
||||
const PromoCode = () => import(/* webpackChunkName: "settings" */'@/components/settings/promoCode');
|
||||
const Site = () => import(/* webpackChunkName: "settings" */'@/components/settings/site');
|
||||
const Subscription = () => import(/* webpackChunkName: "settings" */'@/components/settings/subscription');
|
||||
const Transactions = () => import(/* webpackChunkName: "settings" */'@/components/settings/purchaseHistory');
|
||||
|
||||
// Hall
|
||||
const HallPage = () => import(/* webpackChunkName: "hall" */'@/components/hall/index');
|
||||
const PatronsPage = () => import(/* webpackChunkName: "hall" */'@/components/hall/patrons');
|
||||
@@ -75,10 +40,6 @@ const EquipmentPage = () => import(/* webpackChunkName: "inventory" */'@/compone
|
||||
const StablePage = () => import(/* webpackChunkName: "inventory" */'@/components/inventory/stable/index');
|
||||
|
||||
// Guilds & Parties
|
||||
const GuildIndex = () => import(/* webpackChunkName: "guilds" */ '@/components/groups/index');
|
||||
const TavernPage = () => import(/* webpackChunkName: "guilds" */ '@/components/groups/tavern');
|
||||
const MyGuilds = () => import(/* webpackChunkName: "guilds" */ '@/components/groups/myGuilds');
|
||||
const GuildsDiscoveryPage = () => import(/* webpackChunkName: "guilds" */ '@/components/groups/discovery');
|
||||
const GroupPage = () => import(/* webpackChunkName: "guilds" */ '@/components/groups/group');
|
||||
const GroupPlansAppPage = () => import(/* webpackChunkName: "guilds" */ '@/components/groups/groupPlan');
|
||||
const LookingForParty = () => import(/* webpackChunkName: "guilds" */ '@/components/groups/lookingForParty');
|
||||
@@ -103,7 +64,6 @@ const QuestsPage = () => import(/* webpackChunkName: "shops-quest" */'@/componen
|
||||
const SeasonalPage = () => import(/* webpackChunkName: "shops-seasonal" */'@/components/shops/seasonal/index');
|
||||
const TimeTravelersPage = () => import(/* webpackChunkName: "shops-timetravelers" */'@/components/shops/timeTravelers/index');
|
||||
|
||||
const NotFoundPage = () => import(/* webpackChunkName: "not-found" */'@/components/404');
|
||||
|
||||
Vue.use(VueRouter);
|
||||
|
||||
@@ -188,29 +148,7 @@ const router = new VueRouter({
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/groups',
|
||||
component: GuildIndex,
|
||||
children: [
|
||||
{ name: 'tavern', path: 'tavern', component: TavernPage },
|
||||
{
|
||||
name: 'myGuilds',
|
||||
path: 'myGuilds',
|
||||
component: MyGuilds,
|
||||
},
|
||||
{
|
||||
name: 'guildsDiscovery',
|
||||
path: 'discovery',
|
||||
component: GuildsDiscoveryPage,
|
||||
},
|
||||
{
|
||||
name: 'guild',
|
||||
path: 'guild/:groupId',
|
||||
component: GroupPage,
|
||||
props: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
DEPRECATED_ROUTES,
|
||||
{ path: PAGES.PRIVATE_MESSAGES, name: 'privateMessages', component: MessagesIndex },
|
||||
{
|
||||
name: 'challenges',
|
||||
@@ -235,122 +173,8 @@ const router = new VueRouter({
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/user',
|
||||
component: ParentPage,
|
||||
children: [
|
||||
{ name: 'stats', path: 'stats', component: ProfilePage },
|
||||
{ name: 'achievements', path: 'achievements', component: ProfilePage },
|
||||
{ name: 'profile', path: 'profile', component: ProfilePage },
|
||||
{
|
||||
name: 'settings',
|
||||
path: 'settings',
|
||||
component: Settings,
|
||||
children: [
|
||||
{
|
||||
name: 'site',
|
||||
path: 'site',
|
||||
component: Site,
|
||||
},
|
||||
{
|
||||
name: 'api',
|
||||
path: 'api',
|
||||
component: API,
|
||||
},
|
||||
{
|
||||
name: 'dataExport',
|
||||
path: 'data-export',
|
||||
component: DataExport,
|
||||
},
|
||||
{
|
||||
name: 'promoCode',
|
||||
path: 'promo-code',
|
||||
component: PromoCode,
|
||||
},
|
||||
{
|
||||
name: 'subscription',
|
||||
path: 'subscription',
|
||||
component: Subscription,
|
||||
},
|
||||
{
|
||||
name: 'transactions',
|
||||
path: 'transactions',
|
||||
component: Transactions,
|
||||
meta: {
|
||||
privilegeNeeded: [
|
||||
'userSupport',
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'notifications',
|
||||
path: 'notifications',
|
||||
component: Notifications,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/static',
|
||||
component: StaticWrapper,
|
||||
children: [
|
||||
{
|
||||
name: 'app', path: 'app', component: AppPage, meta: { requiresLogin: false },
|
||||
},
|
||||
{
|
||||
name: 'appleRedirect', path: 'apple-redirect', component: AppleRedirectPage, meta: { requiresLogin: false },
|
||||
},
|
||||
{
|
||||
name: 'clearBrowserData', path: 'clear-browser-data', component: ClearBrowserDataPage, meta: { requiresLogin: false },
|
||||
},
|
||||
{
|
||||
name: 'communityGuidelines', path: 'community-guidelines', component: CommunityGuidelinesPage, meta: { requiresLogin: false },
|
||||
},
|
||||
{
|
||||
name: 'contact', path: 'contact', component: ContactPage, meta: { requiresLogin: false },
|
||||
},
|
||||
{
|
||||
name: 'faq', path: 'faq', component: FAQPage, meta: { requiresLogin: false },
|
||||
},
|
||||
{
|
||||
name: 'chatSunsetFaq', path: 'tavern-and-guilds', component: ChatSunsetFaq, meta: { requiresLogin: false },
|
||||
},
|
||||
{
|
||||
name: 'features', path: 'features', component: FeaturesPage, meta: { requiresLogin: false },
|
||||
},
|
||||
{
|
||||
name: 'groupPlans', path: 'group-plans', component: GroupPlansPage, meta: { requiresLogin: false },
|
||||
},
|
||||
{
|
||||
name: 'home', path: 'home', component: HomePage, meta: { requiresLogin: false },
|
||||
},
|
||||
{
|
||||
name: 'front', path: 'front', component: HomePage, meta: { requiresLogin: false },
|
||||
},
|
||||
{
|
||||
name: 'news', path: 'new-stuff', component: NewsPage, meta: { requiresLogin: false },
|
||||
},
|
||||
{
|
||||
name: 'overview', path: 'overview', component: OverviewPage, meta: { requiresLogin: false },
|
||||
},
|
||||
{
|
||||
name: 'plans', path: 'plans', component: GroupPlansPage, meta: { requiresLogin: false },
|
||||
},
|
||||
{
|
||||
name: 'pressKit', path: 'press-kit', component: PressKitPage, meta: { requiresLogin: false },
|
||||
},
|
||||
{
|
||||
name: 'privacy', path: 'privacy', component: PrivacyPage, meta: { requiresLogin: false },
|
||||
},
|
||||
{
|
||||
name: 'terms', path: 'terms', component: TermsPage, meta: { requiresLogin: false },
|
||||
},
|
||||
{
|
||||
name: 'notFound', path: 'not-found', component: NotFoundPage, meta: { requiresLogin: false },
|
||||
},
|
||||
],
|
||||
},
|
||||
USER_ROUTES,
|
||||
STATIC_ROUTES,
|
||||
{
|
||||
path: '/hall',
|
||||
component: HallPage,
|
||||
@@ -467,6 +291,21 @@ router.beforeEach(async (to, from, next) => {
|
||||
});
|
||||
}
|
||||
|
||||
// Redirect from Guild link to Group Plan where possible
|
||||
if (to.name === 'guild') {
|
||||
await store.dispatch('guilds:getGroupPlans');
|
||||
const { groupPlans } = store.state;
|
||||
const groupPlanIds = groupPlans.data.map(groupPlan => groupPlan._id);
|
||||
if (groupPlanIds.indexOf(to.params.groupId) !== -1) {
|
||||
return next({
|
||||
name: 'groupPlanDetailInformation',
|
||||
params: {
|
||||
groupId: to.params.groupId,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Redirect old challenge urls
|
||||
if (to.hash.indexOf('#/options/groups/challenges/') !== -1) {
|
||||
const splits = to.hash.split('/');
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
export const NotFoundPage = () => import(/* webpackChunkName: "not-found" */'@/components/404');
|
||||
|
||||
export const ProfilePage = () => import(/* webpackChunkName: "user" */'@/components/userMenu/profilePage');
|
||||
@@ -0,0 +1,82 @@
|
||||
// NOTE: when adding a page make sure to implement the `common:setTitle` action
|
||||
|
||||
import { NotFoundPage } from './shared-route-imports';
|
||||
|
||||
const StaticWrapper = () => import(/* webpackChunkName: "entry" */'@/components/static/staticWrapper');
|
||||
const HomePage = () => import(/* webpackChunkName: "entry" */'@/components/static/home');
|
||||
|
||||
const AppleRedirectPage = () => import(/* webpackChunkName: "static" */'@/components/static/appleRedirect');
|
||||
const ClearBrowserDataPage = () => import(/* webpackChunkName: "static" */'@/components/static/clearBrowserData');
|
||||
const CommunityGuidelinesPage = () => import(/* webpackChunkName: "static" */'@/components/static/communityGuidelines');
|
||||
const ContactPage = () => import(/* webpackChunkName: "static" */'@/components/static/contact');
|
||||
const FAQPage = () => import(/* webpackChunkName: "static" */'@/components/static/faq');
|
||||
const FeaturesPage = () => import(/* webpackChunkName: "static" */'@/components/static/features');
|
||||
const GroupPlansPage = () => import(/* webpackChunkName: "static" */'@/components/static/groupPlans');
|
||||
// Commenting out merch page see
|
||||
// https://github.com/HabitRPG/habitica/issues/12039
|
||||
// const MerchPage = () => import(/* webpackChunkName: "static" */'@/components/static/merch');
|
||||
const NewsPage = () => import(/* webpackChunkName: "static" */'@/components/static/newStuff');
|
||||
const OverviewPage = () => import(/* webpackChunkName: "static" */'@/components/static/overview');
|
||||
const PressKitPage = () => import(/* webpackChunkName: "static" */'@/components/static/pressKit');
|
||||
const PrivacyPage = () => import(/* webpackChunkName: "static" */'@/components/static/privacy');
|
||||
const ChatSunsetFaq = () => import(/* webpackChunkName: "static" */'@/components/static/chatSunsetFaq');
|
||||
const TermsPage = () => import(/* webpackChunkName: "static" */'@/components/static/terms');
|
||||
|
||||
|
||||
export const STATIC_ROUTES = {
|
||||
path: '/static',
|
||||
component: StaticWrapper,
|
||||
children: [
|
||||
{
|
||||
name: 'appleRedirect', path: 'apple-redirect', component: AppleRedirectPage, meta: { requiresLogin: false },
|
||||
},
|
||||
{
|
||||
name: 'clearBrowserData', path: 'clear-browser-data', component: ClearBrowserDataPage, meta: { requiresLogin: false },
|
||||
},
|
||||
{
|
||||
name: 'communityGuidelines', path: 'community-guidelines', component: CommunityGuidelinesPage, meta: { requiresLogin: false },
|
||||
},
|
||||
{
|
||||
name: 'contact', path: 'contact', component: ContactPage, meta: { requiresLogin: false },
|
||||
},
|
||||
{
|
||||
name: 'faq', path: 'faq', component: FAQPage, meta: { requiresLogin: false },
|
||||
},
|
||||
{
|
||||
name: 'chatSunsetFaq', path: 'tavern-and-guilds', component: ChatSunsetFaq, meta: { requiresLogin: false },
|
||||
},
|
||||
{
|
||||
name: 'features', path: 'features', component: FeaturesPage, meta: { requiresLogin: false },
|
||||
},
|
||||
{
|
||||
name: 'groupPlans', path: 'group-plans', component: GroupPlansPage, meta: { requiresLogin: false },
|
||||
},
|
||||
{
|
||||
name: 'home', path: 'home', component: HomePage, meta: { requiresLogin: false },
|
||||
},
|
||||
{
|
||||
name: 'front', path: 'front', component: HomePage, meta: { requiresLogin: false },
|
||||
},
|
||||
{
|
||||
name: 'news', path: 'new-stuff', component: NewsPage, meta: { requiresLogin: false },
|
||||
},
|
||||
{
|
||||
name: 'overview', path: 'overview', component: OverviewPage, meta: { requiresLogin: false },
|
||||
},
|
||||
{
|
||||
name: 'plans', path: 'plans', component: GroupPlansPage, meta: { requiresLogin: false },
|
||||
},
|
||||
{
|
||||
name: 'pressKit', path: 'press-kit', component: PressKitPage, meta: { requiresLogin: false },
|
||||
},
|
||||
{
|
||||
name: 'privacy', path: 'privacy', component: PrivacyPage, meta: { requiresLogin: false },
|
||||
},
|
||||
{
|
||||
name: 'terms', path: 'terms', component: TermsPage, meta: { requiresLogin: false },
|
||||
},
|
||||
{
|
||||
name: 'notFound', path: 'not-found', component: NotFoundPage, meta: { requiresLogin: false },
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,71 @@
|
||||
import ParentPage from '@/components/parentPage.vue';
|
||||
import { ProfilePage } from './shared-route-imports';
|
||||
|
||||
|
||||
// Settings
|
||||
const Settings = () => import(/* webpackChunkName: "settings" */'@/components/settings/index');
|
||||
const API = () => import(/* webpackChunkName: "settings" */'@/components/settings/api');
|
||||
const DataExport = () => import(/* webpackChunkName: "settings" */'@/components/settings/dataExport');
|
||||
const Notifications = () => import(/* webpackChunkName: "settings" */'@/components/settings/notifications');
|
||||
const PromoCode = () => import(/* webpackChunkName: "settings" */'@/components/settings/promoCode');
|
||||
const Site = () => import(/* webpackChunkName: "settings" */'@/components/settings/site');
|
||||
const Subscription = () => import(/* webpackChunkName: "settings" */'@/components/settings/subscription');
|
||||
const Transactions = () => import(/* webpackChunkName: "settings" */'@/components/settings/purchaseHistory');
|
||||
|
||||
|
||||
export const USER_ROUTES = {
|
||||
path: '/user',
|
||||
component: ParentPage,
|
||||
children: [
|
||||
{ name: 'stats', path: 'stats', component: ProfilePage },
|
||||
{ name: 'achievements', path: 'achievements', component: ProfilePage },
|
||||
{ name: 'profile', path: 'profile', component: ProfilePage },
|
||||
{
|
||||
name: 'settings',
|
||||
path: 'settings',
|
||||
component: Settings,
|
||||
children: [
|
||||
{
|
||||
name: 'site',
|
||||
path: 'site',
|
||||
component: Site,
|
||||
},
|
||||
{
|
||||
name: 'api',
|
||||
path: 'api',
|
||||
component: API,
|
||||
},
|
||||
{
|
||||
name: 'dataExport',
|
||||
path: 'data-export',
|
||||
component: DataExport,
|
||||
},
|
||||
{
|
||||
name: 'promoCode',
|
||||
path: 'promo-code',
|
||||
component: PromoCode,
|
||||
},
|
||||
{
|
||||
name: 'subscription',
|
||||
path: 'subscription',
|
||||
component: Subscription,
|
||||
},
|
||||
{
|
||||
name: 'transactions',
|
||||
path: 'transactions',
|
||||
component: Transactions,
|
||||
meta: {
|
||||
privilegeNeeded: [
|
||||
'userSupport',
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'notifications',
|
||||
path: 'notifications',
|
||||
component: Notifications,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -148,6 +148,9 @@ export default function () {
|
||||
egg: '',
|
||||
hatchingPotion: '',
|
||||
},
|
||||
bugReportOptions: {
|
||||
question: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -336,5 +336,7 @@
|
||||
"hatchingPotionWindup": "الزنبرك",
|
||||
"hatchingPotionPorcelain": "الخزف الملون",
|
||||
"hatchingPotionBirchBark": "لحاء الشجر",
|
||||
"hatchingPotionRuby": "الياقوت"
|
||||
"hatchingPotionRuby": "الياقوت",
|
||||
"foodPieRed": "فطيرة الكرز الأحمر",
|
||||
"hatchingPotionTeaShop": "متجر الشاي"
|
||||
}
|
||||
|
||||
@@ -115,5 +115,21 @@
|
||||
"achievementSeasonalSpecialistText": "Dokončil/a jsi všechny Jarní a Zimní sezónní úkoly: Honba za vajíčky, Pastičkář Santa, a najdi Cuba!",
|
||||
"achievementWildBlueYonderText": "Ochočil/a všechny zvířata z Modré Cukrové Vaty.",
|
||||
"achievementWildBlueYonderModalText": "Ochočil/a jsi všechny mazlíčky z Modré Cukrové Vaty!",
|
||||
"achievementDomesticatedText": "Vylíhl/a všechna standardní zbarvení domácích zvířat: Fretka, morče, kohout, létající prasátko, krysa, králík, kůň a kráva!"
|
||||
"achievementDomesticatedText": "Vylíhl/a všechna standardní zbarvení domácích zvířat: Fretka, morče, kohout, létající prasátko, krysa, králík, kůň a kráva!",
|
||||
"achievementWildBlueYonder": "Divoký modrý zázrak",
|
||||
"achievementGroupsBeta2022": "Interaktivní beta tester",
|
||||
"achievementGroupsBeta2022Text": "Vy a vaše skupina jste poskytli neocenitelnou zpětnou vazbu, která pomohla společnosti Habitica při testování.",
|
||||
"achievementWoodlandWizard": "Lesní čaroděj",
|
||||
"achievementWoodlandWizardModalText": "Nasbírali jste všechna lesní zvířátka!",
|
||||
"achievementReptacularRumbleModalText": "Nasbíral jsi všechny plazí mazlíčky!",
|
||||
"achievementBirdsOfAFeather": "Ptáci s pírkem",
|
||||
"achievementBirdsOfAFeatherModalText": "Sesbíral jsi všechna létající zvířátka!",
|
||||
"achievementBoneToPick": "Kosti k vybírání",
|
||||
"achievementZodiacZookeeperModalText": "Nasbíral jsi všechna zvířata zvěrokruhu!",
|
||||
"achievementShadyCustomer": "Stínový zákazník",
|
||||
"achievementShadyCustomerText": "Shromáždil všechna stínová zvířata.",
|
||||
"achievementShadyCustomerModalText": "Sesbíral jsi všechna stínová zvířátka!",
|
||||
"achievementShadeOfItAll": "Odstín toho všeho",
|
||||
"achievementShadeOfItAllText": "Zkrotil všechny stínové držáky.",
|
||||
"achievementShadeOfItAllModalText": "Zkrotil jsi všechny stínové držáky!"
|
||||
}
|
||||
|
||||
@@ -570,7 +570,7 @@
|
||||
"backgroundMysticalObservatoryNotes": "Deine Bestimmung steht in den Sternen; vom Mystischen Observatorium aus kannst Du sie lesen.",
|
||||
"backgroundMysticalObservatoryText": "Mystisches Observatorium",
|
||||
"backgrounds112020": "Set 78: Veröffentlicht im November 2020",
|
||||
"backgroundHolidayHearthNotes": "Entspanne, wärme und trockne deine Körperteile an einem Feierlichen Feuer.",
|
||||
"backgroundHolidayHearthNotes": "Entspanne, wärme und trockne deine Körperteile an einem feierlichen Feuer.",
|
||||
"backgroundHolidayHearthText": "Feierliches Feuer",
|
||||
"backgroundInsideAnOrnamentNotes": "Lasse Deine Festtagsstimmung aus dem Inneren dieses Baumschmuckes erstrahlen.",
|
||||
"backgroundInsideAnOrnamentText": "Im Baumschmuck",
|
||||
@@ -794,5 +794,12 @@
|
||||
"backgroundBirthdayBashNotes": "Habitica feiert eine Geburtstagsparty und alle sind eingeladen!",
|
||||
"eventBackgrounds": "Ereignis-Hintergründe",
|
||||
"backgroundBirthdayBashText": "Geburtstagsparty",
|
||||
"backgroundInsideACrystalNotes": "Schaue aus einem Kristall hinaus."
|
||||
"backgroundInsideACrystalNotes": "Schaue aus einem Kristall hinaus.",
|
||||
"backgrounds072023": "SET 110: Veröffentlicht im Juli 2023",
|
||||
"backgroundOnAPaddlewheelBoatText": "Auf einem Schaufelradboot",
|
||||
"backgroundOnAPaddlewheelBoatNotes": "Fahre mit einem Schaufelradboot.",
|
||||
"backgroundColorfulCoralText": "Kunterbunte Koralle",
|
||||
"backgroundColorfulCoralNotes": "Tauche zwischen kunterbunten Korallen.",
|
||||
"backgroundBoardwalkIntoSunsetText": "Promenade im Sonnenuntergang",
|
||||
"backgroundBoardwalkIntoSunsetNotes": "Schlendere über die Promenade in den Sonnenuntergang."
|
||||
}
|
||||
|
||||
@@ -186,5 +186,6 @@
|
||||
"chatCastSpellParty": "<%= username %> wendet <%= spell %> auf Deine Party an.",
|
||||
"chatCastSpellUser": "<%= username %> wendet <%= spell %> auf <%= target %> an.",
|
||||
"purchasePetItemConfirm": "Dieser Einkauf würde die Anzahl der Gegenstände überschreiten, die Du zum Schlüpfen aller möglichen <%= itemText %>-Tiere benötigst. Bist du sicher?",
|
||||
"notEnoughGold": "Nicht genügend Gold."
|
||||
"notEnoughGold": "Nicht genügend Gold.",
|
||||
"chatCastSpellPartyTimes": "<%= username %> verwendet <%= spell %> <%= times %> Male für Deine Party <%= times %>."
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
"commGuideList02D": "<strong>Halte die Diskussionen für alle Altersgruppen angemessen</strong>. Das heißt, Erwachsenenthemen in öffentlichen Bereichen zu vermeiden. Viele junge Habiticaner und Menschen mit verschiedenen Hintergründen nutzen diese Seite. Wir wollen unsere Gemeinschaft so angenehm und inklusiv wie möglich gestalten.",
|
||||
"commGuideList02E": "<strong>Vermeide vulgäre Ausdrücke. Dazu gehören auch mildere, religiöse Ausdrücke, die anderswo möglicherweise akzeptiert werden, oder verschleierte Schimpfwörter</strong>. Unter uns sind Menschen aus allen religiösen und kulturellen Hintergründen und wir wollen, dass sich alle im öffentlichen Raum wohl fühlen. <strong>Wenn Dir ein Moderator oder Mitarbeiter mitteilt, dass ein bestimmter Ausdruck in Habitica nicht erlaubt ist, selbst wenn er Dir vielleicht nicht problematisch vorkommt, ist diese Entscheidung endgültig</strong>. Zusätzlich werden verbale Angriffe jeder Art strenge Konsequenzen haben, da sie auch unsere Nutzungsbedingungen verletzen.",
|
||||
"commGuideList02F": "Vermeide längere Diskussionen über spaltende Themen in der Taverne und wenn sie außerhalb des Themenbereichs liegen. Wenn jemand etwas sagt, das zwar von den Richtlinien her erlaubt ist, das Dich aber verletzt, dann ist es in Ordnung, diese Person höflich darauf hinzuweisen. Wenn Dir eine Person sagt, dass ihr Dein Verhalten unangenehm ist, nimm Dir Zeit, darüber zu reflektieren, anstatt im Zorn zu antworten. Aber wenn Du das Gefühl hast, dass ein Gespräch hitzig, übermäßig emotional, oder verletzend wird, dann <strong>lass dich nicht darauf ein. Melde stattdessen die Beiträge, um uns darüber in Kenntnis zu setzen.</strong> Moderatoren werden so schnell wie möglich antworten. Du kannst auch eine E-Mail an <a href='mailto:admin@habitica.com' target='_blank'>admin@habitica.com</a> senden und gegebenenfalls Screenshots anhängen.",
|
||||
"commGuideList02G": "<strong>Erfülle alle Moderator-Anfragen sofort</strong>. Diese könnten Folgendes beinhalten, ist aber nicht darauf beschränkt: Dich aufzufordern, deine Beiträge in einem bestimmten Bereich zu begrenzen, dein Profil zu bearbeiten, um ungeeignete Inhalte zu entfernen, dich zu bitten, deine Diskussion in einen geeigneteren Bereich zu verschieben, etc. Diskutiere nicht mit Moderatoren. Solltest du mit einer Entscheidung unzufrieden sein, oder anderes Feedback zur Moderation haben, sende eine E-mail an <a href='mailto:admin@habitica.com' target='_blank'>admin@habitica.com</a> um unseren Community Manager zu kontaktieren.",
|
||||
"commGuideList02G": "<strong>Erfülle alle Mitarbeitenden-Anfragen sofort</strong>. Diese könnten Folgendes beinhalten, ist aber nicht darauf beschränkt: Dich aufzufordern, deine Beiträge in einem bestimmten Bereich zu begrenzen, dein Profil zu bearbeiten, um ungeeignete Inhalte zu entfernen, dich zu bitten, deine Diskussion in einen geeigneteren Bereich zu verschieben, etc. Diskutiere nicht mit Mitarbeitenden. Solltest du mit einer Entscheidung unzufrieden sein, oder anderes Feedback zur Mitarbeitenden haben, sende eine E-mail an <a href='mailto:admin@habitica.com' target='_blank'>admin@habitica.com</a> um unseren Community Manager zu kontaktieren.",
|
||||
"commGuideList02J": "<strong>Poste keinen Spam</strong>. Spamming kann Folgendes beinhalten, ist aber nicht beschränkt auf: das Posten desselben Kommentars oder derselben Frage an mehreren Stellen, <strong>das Posten von Links ohne Erklärung oder Kontext</strong>, das Posten unsinniger Nachrichten, das Posten mehrerer Werbebotschaften für eine Gilde, Party, oder Herausforderung, oder das Posten vieler Nachrichten hintereinander. Wenn Du irgendeinen Nutzen daraus ziehst, wenn jemand auf einen Link klickt, musst Du das im Text Deiner Nachricht offenlegen, sonst wird sie auch als Spam betrachtet. Mods können gegebenenfalls nach ihrem Ermessen entscheiden, was Spam ausmacht.",
|
||||
"commGuideList02K": "<strong>Bitte vermeide große Überschriften in öffentlichen Chats, vor allem in der Taverne.</strong> Ähnlich wie bei GROSSBUCHSTABEN liest sich der Text, als ob Du schreien würdest, und beeinträchtigt die gemütliche Atmosphäre.",
|
||||
"commGuideList02L": "<strong>Wir raten Dir dringend davon ab, persönliche Informationen - besonders solche, mit denen Du identifiziert werden könntest - in öffentlichen Chats zu teilen.</strong> Zu den identifizierenden Informationen gehören unter anderem: Deine Adresse, Deine E-Mail-Adresse und Dein API-Token/Passwort. Dies dient nur Deiner Sicherheit! Mitarbeiter oder Moderatoren werden solche Beiträge nach eigenem Ermessen entfernen. Wenn Du nach persönlichen Informationen in einer privaten Gilde, Party oder per PN gefragt wirst, empfehlen wir dringend, dass Du höflich ablehnst und Mitarbeiter und Moderatoren informierst, indem Du entweder 1) den Beitrag über das Fähnchen meldest, oder 2) eine E-Mail an <a href='mailto:admin@habitica.com' target='_blank'>admin@habitica.com</a> schreibst und Screenshots anhängst.",
|
||||
@@ -123,7 +123,7 @@
|
||||
"commGuideList01B": "Unzulässig: Jegliche Kommunikation die gewalttätig/aggressiv, bedrohlich, deskrimienierungsverherlichend o.ä. ist. Inklusive Memes, Bilder/Grafiken und Witze.",
|
||||
"commGuideList01A": "Die Allgemeinen Geschäftsbedingungen gelten für alle Bereiche, einschließlich privater Gilden, Party-Chat und Nachrichten.",
|
||||
"commGuideList01C": "Alle Diskussionen müssen für alle Altersgruppen angemessen und frei von Kraftausdrücken sein.",
|
||||
"commGuideList01D": "Haltet euch bitte an die Anweisungen der Moderatoren.",
|
||||
"commGuideList01D": "Haltet euch bitte an die Anweisungen der Mitarbeitenden.",
|
||||
"commGuideList01E": "Fange in der Taverne keine Streitgespräche an und lass Dich nicht auf solche ein.",
|
||||
"commGuideList01F": "Kein Betteln um bezahlte Artikel, kein Spamming oder große Überschriften/Großbuchstaben.",
|
||||
"commGuidePara017": "Hier ist die Kurzfassung, aber wir möchten Dich ermutigen, weiter unten mehr Details zu erfahren:",
|
||||
|
||||
@@ -65,5 +65,14 @@
|
||||
"faqQuestion15": "Wie funktioniert die Suche nach einer Party?",
|
||||
"iosFaqAnswer14": "Wenn du Habitica mit anderen erleben möchtest, aber keine anderen Spieler kennst, ist die Suche nach einer Party die beste Option! Wenn du bereits andere Spieler kennst, die eine Party haben, kannst du deinen @Benutzernamen mit ihnen teilen, um eingeladen zu werden. Alternativ kannst du auch eine neue Party gründen und sie mit ihrem @Nutzernamen oder ihrer E-Mail-Adresse einladen.\n\nUm eine Party zu erstellen oder zu suchen, wähle \"Party\" im Navigationsmenü und wähle dann die Option, die dir am besten gefällt.",
|
||||
"webFaqAnswer14": "Wenn du Habitica mit anderen erleben möchtest, aber keine anderen Spieler kennst, ist die Suche nach einer Party die beste Option! Wenn du bereits andere Spieler kennst, die eine Party haben, kannst du deinen @Benutzernamen mit ihnen teilen, um eingeladen zu werden. Alternativ kannst du auch eine neue Party gründen und sie mit ihrem @Nutzernamen oder ihrer E-Mail-Adresse einladen.\n\nUm eine Party zu erstellen oder zu suchen, wähle \"Party\" im Navigationsmenü und wähle dann die Option, die dir am besten gefällt.",
|
||||
"androidFaqAnswer14": "Wenn du Habitica mit anderen erleben möchtest, aber keine anderen Spieler kennst, ist die Suche nach einer Party die beste Option! Wenn du bereits andere Spieler kennst, die eine Party haben, kannst du deinen @Benutzernamen mit ihnen teilen, um eingeladen zu werden. Alternativ kannst du auch eine neue Party gründen und sie mit ihrem @Nutzernamen oder ihrer E-Mail-Adresse einladen.\n\nUm eine Party zu erstellen oder zu suchen, wähle \"Party\" im Navigationsmenü und wähle dann die Option, die dir am besten gefällt."
|
||||
"androidFaqAnswer14": "Wenn du Habitica mit anderen erleben möchtest, aber keine anderen Spieler kennst, ist die Suche nach einer Party die beste Option! Wenn du bereits andere Spieler kennst, die eine Party haben, kannst du deinen @Benutzernamen mit ihnen teilen, um eingeladen zu werden. Alternativ kannst du auch eine neue Party gründen und sie mit ihrem @Nutzernamen oder ihrer E-Mail-Adresse einladen.\n\nUm eine Party zu erstellen oder zu suchen, wähle \"Party\" im Navigationsmenü und wähle dann die Option, die dir am besten gefällt.",
|
||||
"iosFaqAnswer15": "Nachdem du \"Nach einer Party suchen\" ausgewählt hast, wirst du zu einer Liste von Spielern hinzugefügt, die einer Party beitreten möchten. Gruppenleiter können diese Liste einsehen und Einladungen verschicken. Sobald du eine Einladung erhalten hast, kannst du sie über deine Benachrichtigungen annehmen und der Party deiner Wahl beitreten!\n\nDu kannst mehrere Einladungen zu verschiedenen Partys erhalten. Du kannst jedoch immer nur Mitglied einer Party sein.",
|
||||
"webFaqAnswer24": "Wir haben in der Android-Version 4.2 die Möglichkeit hinzugefügt, nach Partys und Partymitgliedern zu suchen. Stelle sicher, dass du die App geupdated hast, um die Funktion verwenden zu können. Einzelspieler können nach einer Party vom Partymenü aus suchen, wenn sie sich nicht bereits in einer Party befinden. Anführer von Partys können die Liste von Spielern, die eine Party suchen, aufrufen, indem sie \"Neue Party-Mitglieder suchen\" über ihrer eigenen Party-Mitglieder-Liste anklicken.",
|
||||
"faqQuestion23": "Wie filtere ich die Liste von Mitgliedern, welche nach einer Party suchen?",
|
||||
"iosFaqAnswer23": "Aktuell gibt es leider keine Möglichkeit, die Liste mit Mitgliedern, welche nach einer Party suchen, zu filtern. Wir arbeiten jedoch aktuell an einem Filtersystem, mit welchem man nach Klasse, Level und Sprache filtern kann.",
|
||||
"androidFaqAnswer23": "Aktuell gibt es keine Möglichkeit, die Liste mit Mitgliedern, welche nach einer Party suchen, zu filtern. Wir arbeiten jedoch an einer Funktion, mit welcher man nach Klasse, Level und Sprache filtern kann.",
|
||||
"webFaqAnswer23": "Aktuell gibt es keine Möglichkeit, die Liste mit Mitgliedern, welche nach einer Party suchen, zu filtern. Wir arbeiten jedoch an einer Funktion, mit welcher man nach Klasse, Level und Sprache filtern kann.",
|
||||
"iosFaqAnswer22": "Sobald du einer Party beigetreten bist, bekommst du keine Einladungen für andere Partys mehr. Wenn du künftige Einladungen und Nachrichten von einem bestimmten Mitglied verhindern möchtest, besuche das betroffene Profil und klicke den \"Blockieren\"-Button an. Auf dem Handy müssen dafür die drei Punkte in der oberen Ecke und dann \"Blockieren\" ausgewählt werden.\n\nWenn du vermutest, dass ein anderer Spieler unsere Community-Richtlinien mittels Benutzername, Profilangaben oder privat versendeten Nachrichten verstößt, melde dies bitte oder wende dich direkt via eMail an uns über admin@habitica.com",
|
||||
"webFaqAnswer22": "Sobald du einer Party beigetreten bist, bekommst du keine Einladungen für andere Partys mehr. Wenn du künftige Einladungen und Nachrichten von einem bestimmten Mitglied verhindern möchtest, besuche das betroffene Profil und klicke den \"Blockieren\"-Button an. Auf dem Handy müssen dafür die drei Punkte in der oberen Ecke und dann \"Blockieren\" ausgewählt werden.\n\nWenn du vermutest, dass ein anderer Spieler unsere Community-Richtlinien mittels Benutzername, Profilangaben oder privat versendeten Nachrichten verstößt, melde dies bitte oder wende dich direkt via eMail an uns über admin@habitica.com",
|
||||
"faqQuestion24": "Wie suche ich nach einer Party auf Android oder iOS?"
|
||||
}
|
||||
|
||||
@@ -134,5 +134,6 @@
|
||||
"newStuffPostedOn": "Veröffentlicht am <%= publishDate %> um <%= publishTime %>",
|
||||
"helpSupportHabitica": "Hilf Habitica zu unterstützen",
|
||||
"groupsPaymentSubBilling": "Dein nächstes Rechnungsdatum ist <strong><%= renewalDate %></strong>.",
|
||||
"groupsPaymentAutoRenew": "Dieses Abonnement läuft automatisch weiter, bis es gekündigt wird. Du kannst es im Gruppen-Abrechnungs-Tab kündigen."
|
||||
"groupsPaymentAutoRenew": "Dieses Abonnement läuft automatisch weiter, bis es gekündigt wird. Du kannst es im Gruppen-Abrechnungs-Tab kündigen.",
|
||||
"sellItems": "Items verkaufen"
|
||||
}
|
||||
|
||||
@@ -150,5 +150,8 @@
|
||||
"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!"
|
||||
"achievementDinosaurDynastyModalText": "You collected all the bird and dinosaur pets!",
|
||||
"achievementBonelessBoss": "Boneless Boss",
|
||||
"achievementBonelessBossText": "Has hatched all standard colors of invertebrate pets: Beetle, Butterfly, Cuttlefish, Nudibranch, Octopus, Snail, and Spider!",
|
||||
"achievementBonelessBossModalText": "You collected all the invertebrate pets!"
|
||||
}
|
||||
|
||||
@@ -899,6 +899,22 @@
|
||||
"backgroundBoardwalkIntoSunsetText": "Boardwalk into the Sunset",
|
||||
"backgroundBoardwalkIntoSunsetNotes": "Stroll on a Boardwalk into the Sunset.",
|
||||
|
||||
"backgrounds082023": "SET 111: Released August 2023",
|
||||
"backgroundBonsaiCollectionText": "Bonsai Collection",
|
||||
"backgroundBonsaiCollectionNotes": "Admire a gorgeous Bonsai Collection.",
|
||||
"backgroundDreamyIslandText": "Dreamy Island",
|
||||
"backgroundDreamyIslandNotes": "Enjoy the scenery on a Dreamy Island.",
|
||||
"backgroundRockGardenText": "Rock Garden",
|
||||
"backgroundRockGardenNotes": "Relax in a Rock Garden.",
|
||||
|
||||
"backgrounds092023": "SET 112: Released September 2023",
|
||||
"backgroundMovingDayText": "Moving Day",
|
||||
"backgroundMovingDayNotes": "Pack up for Moving Day.",
|
||||
"backgroundCoveredBridgeInAutumnText": "Covered Bridge in Autumn",
|
||||
"backgroundCoveredBridgeInAutumnNotes": "Traverse a Covered Bridge in Autumn.",
|
||||
"backgroundBaobabForestText": "Baobab Forest",
|
||||
"backgroundBaobabForestNotes": "Gaze in wonder at the Baobab Forest.",
|
||||
|
||||
"timeTravelBackgrounds": "Steampunk Backgrounds",
|
||||
"backgroundAirshipText": "Airship",
|
||||
"backgroundAirshipNotes": "Become a sky sailor on board your very own Airship.",
|
||||
|
||||
@@ -3,81 +3,57 @@
|
||||
|
||||
"lastUpdated": "Last updated:",
|
||||
"commGuideHeadingWelcome": "Welcome to Habitica!",
|
||||
"commGuidePara001": "Greetings, adventurer! Welcome to Habitica, the land of productivity, healthy living, and the occasional rampaging gryphon. We have a cheerful community full of helpful people supporting each other on their way to self-improvement. To fit in, all it takes is a positive attitude, a respectful manner, and the understanding that everyone has different skills and limitations -- including you! Habiticans are patient with one another and try to help whenever they can.",
|
||||
"commGuidePara002": "To help keep everyone safe, happy, and productive in the community, we do have some guidelines. We have carefully crafted them to make them as friendly and easy-to-read as possible. Please take the time to read them before you start chatting.",
|
||||
"commGuidePara003": "These rules apply to all of the social spaces we use, including (but not necessarily limited to) Trello, GitHub, Weblate, and the Habitica Wiki on Fandom. As communities grow and change, their rules may adapt from time to time. When there are substantive changes to the community rules listed here, you'll hear about it in a Bailey announcement and/or our social media!",
|
||||
"commGuidePara001": "Greetings, adventurer! Welcome to Habitica, the land of productivity, healthy living, and the occasional rampaging gryphon.",
|
||||
"commGuidePara002": "To help keep everyone safe, happy, and productive, we do have some guidelines for Challenges, player profiles, Party chat, and private messages. We have carefully crafted these Guidelines to make them as friendly and easy-to-read as possible. Please take the time to read them before you start interacting with fellow players.",
|
||||
"commGuidePara003": "These rules may be adapted from time to time. When there are substantive changes to the community rules listed here, you'll hear about it in a Bailey announcement and/or our social media!",
|
||||
"commGuideHeadingInteractions": "Interactions in Habitica",
|
||||
"commGuidePara015": "Habitica has two kinds of social spaces: public, and private. Public spaces include the Tavern, Public Guilds, GitHub, Trello, and the Wiki. Private spaces are Private Guilds, Party chat, and Private Messages. All Display Names and @usernames must comply with the public space guidelines. To change your Display Name and/or @username, on mobile go to Menu > Settings > Profile. On web, go to User > Settings.",
|
||||
"commGuidePara016": "When navigating the public spaces in Habitica, there are some general rules to keep everyone safe and happy.",
|
||||
|
||||
"commGuidePara017": "Here's the quick version, but we encourage you to read in more detail below:",
|
||||
"commGuideList01F": "Please flag posts that break our Community Guidelines or TOS.",
|
||||
"commGuideList01A": "Terms & Conditions apply across all spaces, including private guilds, party chat, and messages.",
|
||||
"commGuideList01B": "Prohibited: Any communication that is violent, threatening, promoting of discrimination etc. including memes, images, and jokes.",
|
||||
"commGuideList01C": "All discussions must be appropriate for all ages and be free of profanity.",
|
||||
"commGuideList01D": "Please comply with staff requests.",
|
||||
"commGuideList01E": "<strong>Do not instigate or engage in contentious conversation in the Tavern.</strong>",
|
||||
"commGuideList01F": "No begging for paid items, spamming, or large header text/all caps.",
|
||||
|
||||
"commGuideList02N": "<strong>Flag and report posts that break these Guidelines or the Terms of Service.</strong> We will handle them as quickly as possible. You may also notify staff via <a href='mailto:admin@habitica.com' target='_blank'>admin@habitica.com</a> but flags are the fastest way to get help.",
|
||||
"commGuideList02A": "<strong>Respect each other</strong>. Be courteous, kind, friendly, and helpful. Remember: Habiticans come from all backgrounds and have had wildly divergent experiences. This is part of what makes Habitica so cool! Building a community means respecting and celebrating our differences as well as our similarities.",
|
||||
"commGuideList02B": "<strong>Obey all of the <a href='https://habitica.com/static/terms' target='_blank'>Terms and Conditions</a></strong> in both public and private spaces.",
|
||||
"commGuideList02G": "<strong>Comply immediately with any Staff request.</strong> This could include, but is not limited to, requesting you limit your posts in a particular space, editing your profile to remove unsuitable content, asking you to move your discussion to a more suitable space, etc. Do not argue with staff. If you have concerns or comments about staff actions, email <a href='mailto:admin@habitica.com' target='_blank'>admin@habitica.com</a> to contact our community manager.",
|
||||
"commGuidePara015": "Habitica has a few spaces where you may interact with other players. These include private chat contexts (private messages and Party chat) as well as the Looking for Party feature and Challenges.",
|
||||
"commGuidePara016": "When navigating the social components of Habitica, there are some general rules to keep everyone safe and happy.",
|
||||
|
||||
"commGuideList01A": "Our Guidelines and Terms of Service apply in Challenges, Parties, player profiles, and private messages.",
|
||||
"commGuideList02C": "<strong>Do not post images or text that are violent, threatening, or sexually explicit/suggestive, or that promote discrimination, bigotry, racism, sexism, hatred, harassment or harm against any individual or group</strong>. Not even as a joke or meme. This includes slurs as well as statements. Not everyone has the same sense of humor, and so something that you consider a joke may be hurtful to another.",
|
||||
"commGuideList02D": "<strong>Keep discussions appropriate for all ages</strong>. This means avoiding adult topics in public spaces. We have many young Habiticans who use the site, and people come from all walks of life. We want our community to be as comfortable and inclusive as possible.",
|
||||
"commGuideList02E": "<strong>Avoid profanity. This includes abbreviated or obscured profanity.</strong> We have people from all religious and cultural backgrounds, and we want to make sure that all of them feel comfortable in public spaces. <strong>If a staff member tells you that a term is disallowed on Habitica, even if it is a term that you did not realize was problematic, that decision is final.</strong> Additionally, slurs will be dealt with very severely, as they are also a violation of the Terms of Service.",
|
||||
"commGuideList02F": "Avoid extended discussions of divisive topics in the Tavern and where it would be off-topic. If someone mentions something that is allowed by the guidelines but which is hurtful to you, it's okay to politely let them know that. If someone tells you you've made them uncomfortable, take time to reflect instead of responding in anger. But if you feel that a conversation is getting heated, overly emotional, or hurtful, <strong>cease to engage. Instead, report the posts to let us know about it. </strong>Staff will respond as quickly as possible. You may also email <a href='mailto:admin@habitica.com' target='_blank'>admin@habitica.com</a> and include screenshots if they may be helpful.",
|
||||
"commGuideList02M": "Do not ask or beg for gems, subscriptions or membership in Group Plans. This is not allowed in the Tavern, public or private chat spaces, or in PMs. If you receive messages asking for paid items, please report them by flagging. Repeated or severe gem or subscription begging, especially after a warning, may result in an account ban.",
|
||||
"commGuideList02J": "<strong>Do not spam</strong>. Spamming may include, but is not limited to: posting the same comment or query in multiple places, <strong>posting links without explanation or context</strong>, posting nonsensical messages, posting multiple promotional messages about a Guild, Party or Challenge, or posting many messages in a row. If people clicking on a link will result in any benefit to you, you need to disclose that in the text of your message or that will also be considered spam. Staff may decide what constitutes spam at their discretion.",
|
||||
"commGuideList02K": "<strong>Avoid posting large header text in the public chat spaces, particularly the Tavern</strong>. Much like ALL CAPS, it reads as if you were yelling, and interferes with the comfortable atmosphere.",
|
||||
"commGuideList02L": "<strong>We highly discourage the exchange of personal information -- particularly information that can be used to identify you -- in public chat spaces</strong>. Identifying information can include but is not limited to: your address, your email address, and your API token/password. This is for your safety! Staff may remove such posts at their discretion. If you are asked for personal information in a private Guild, Party, or PM, we highly recommend that you politely refuse and alert the staff by either 1) flagging the message, or 2) emailing <a href='mailto:admin@habitica.com' target='_blank'>admin@habitica.com</a> and including screenshots.",
|
||||
"commGuidePara019": "<strong>In private spaces</strong>, users have more freedom to discuss whatever topics they would like, but they still may not violate the Terms and Conditions, including posting slurs or any discriminatory, violent, or threatening content. Note that, because Challenge names appear in the winner's public profile, ALL Challenge names must obey the public space guidelines, even if they appear in a private space.",
|
||||
"commGuidePara020": "<strong>Private Messages (PMs)</strong> have some additional guidelines. If someone has blocked you, do not contact them elsewhere to ask them to unblock you. Additionally, you should not send PMs to someone asking for support (since public answers to support questions are helpful to the community). Finally, do not send anyone PMs begging for paid content of any kind.",
|
||||
"commGuidePara020A": "<strong>If you see a post or private message that you believe is in violation of the public space guidelines outlined above, or if you see a post or private message that concerns you or makes you uncomfortable, you can bring it to the attention of Staff by clicking the flag icon to report it</strong>. A Staff member will respond to the situation as soon as possible. Please note that intentionally reporting innocent posts is an infraction of these Guidelines (see below in \"Infractions\"). You can also contact the Staff by emailing <a href='mailto:admin@habitica.com' target='_blank'>admin@habitica.com</a> You may want to do this if there are multiple problematic posts by the same person in different Guilds, or if the situation requires some explanation. You may contact us in your native language if that is easier for you: we may have to use Google Translate, but we want you to feel comfortable about contacting us if you have a problem.",
|
||||
"commGuidePara021": "Furthermore, some public spaces in Habitica have additional guidelines.",
|
||||
|
||||
"commGuideHeadingTavern": "The Tavern",
|
||||
"commGuidePara022": "The Tavern is the main spot for Habiticans to mingle. Daniel the Innkeeper keeps the place spic-and-span, and Lemoness will happily conjure up some lemonade while you sit and chat. Just keep in mind…",
|
||||
"commGuidePara023": "<strong>Conversation tends to revolve around casual chatting and productivity or life improvement tips</strong>. Because the Tavern chat can only hold 200 messages, <strong>it isn't a good place for prolonged conversations on topics, especially sensitive or contentious ones</strong> (ex. politics, religion, depression, whether or not goblin-hunting should be banned, etc.). These conversations should be taken to an applicable Guild. Staff may direct you to a suitable Guild, but it is ultimately your responsibility to find and post in the appropriate place.",
|
||||
"commGuidePara024": "<strong>Don't discuss anything addictive in the Tavern</strong>. Many people use Habitica to try to quit their bad Habits. Hearing people talk about addictive/illegal substances may make this much harder for them! Respect your fellow Tavern-goers and take this into consideration. This includes, but is not exclusive to: smoking, alcohol, pornography, gambling, and drug use/abuse.",
|
||||
|
||||
"commGuideHeadingPublicGuilds": "Public Guilds",
|
||||
"commGuidePara029": "<strong>Public Guilds are much like the Tavern, except that instead of being centered around general conversation, they have a focused theme</strong>. Public Guild chat should focus on this theme. For example, members of the Wordsmiths Guild might be cross if the conversation is suddenly focusing on gardening instead of writing, and a Dragon-Fanciers Guild might not have any interest in deciphering ancient runes. Some Guilds are more lax about this than others, but in general, <strong>try to stay on topic</strong>!",
|
||||
"commGuidePara031": "Some public Guilds will contain sensitive topics such as depression, religion, politics, etc. This is fine as long as the conversations therein do not violate any of the Terms and Conditions or Public Space Rules, and as long as they stay on topic.",
|
||||
"commGuidePara033": "<strong>Public Guilds may NOT contain 18+ content. If they plan to regularly discuss sensitive content, they should say so in the Guild description</strong>. This is to keep Habitica safe and comfortable for everyone.",
|
||||
"commGuidePara035": "<strong>If the Guild in question has different kinds of sensitive issues, it is respectful to your fellow Habiticans to include a warning (ex. \"Warning: references self-harm\")</strong>. These may be characterized as trigger warnings and/or content notes, and Guilds may have their own rules in addition to those given here. Habitica staff may still remove this material at their discretion.",
|
||||
"commGuidePara036": "Additionally, the sensitive material should be topical -- bringing up self-harm in a Guild focused on fighting depression may make sense, but is probably less appropriate in a music Guild. If you see someone who is repeatedly violating this guideline, especially after several requests, please report the posts.",
|
||||
"commGuidePara037": "<strong>No Guilds, Public or Private, should be created for the purpose of attacking any group or individual</strong>. Creating such a Guild is grounds for an instant ban. Fight bad habits, not your fellow adventurers!",
|
||||
"commGuidePara038": "<strong>All Tavern Challenges and Public Guild Challenges must comply with these rules as well</strong>.",
|
||||
"commGuideList02N": "<strong>Report anything you see that breaks these Guidelines or our Terms of Service</strong>. You can report a message directly or notify staff via <a href='mailto:admin@habitica.com' target='_blank'>admin@habitica.com</a> for violations in profiles or Challenges. We will handle them as quickly as possible. You may contact us in your native language if that is easier for you: we may have to use Google Translate, but we want you to feel comfortable about contacting us if you have a problem.",
|
||||
"commGuideList02H": "<strong>All Display Names and @usernames must comply with the Terms of Service</strong>. To change your Display Name and/or @username: on mobile go to Menu > Settings > Account. On web, go to Settings from the user icon in the top navigation.",
|
||||
"commGuideList02A": "<strong>Respect each other</strong>. Be courteous, kind, friendly, and helpful. Remember: Habiticans come from all backgrounds and have had wildly divergent experiences.",
|
||||
"commGuideList02I": "<strong>Challenge names should be appropriate for all spaces, as they will appear in the winner's public profile</strong>. Keep this in mind when creating Challenges as we may be forced to edit the record on their profile if there is a report.",
|
||||
"commGuideList02G": "<strong>Comply immediately with any Staff request.</strong> This could include, but is not limited to, requesting you limit your posts in a particular space, editing your profile to remove unsuitable content, etc. Do not argue with Staff. If you have concerns or comments about Staff actions, email <a href='mailto:admin@habitica.com' target='_blank'>admin@habitica.com</a> to contact our community manager.",
|
||||
"commGuideList02D": "<strong>Be mindful that Habiticans are of all ages and backgrounds</strong>. Challenges and player profiles should not mention adult topics, use profanity, or promote contention or conflict.",
|
||||
"commGuideList02E": "<strong>If a staff member tells you that a term is disallowed on Habitica, even if it is a term that you did not realize was problematic, that decision is final.</strong> Additionally, slurs will be dealt with very severely, as they are also a violation of the Terms of Service.",
|
||||
"commGuideList02O": "<strong>Parties may create their own chat rules for members’ comfort and preferences</strong>. However, the admins cannot enforce chat rules in these private spaces unless there is a breach of the Terms of Service, including harassment. If someone in your Party is causing issues, we encourage the Party leader to remove them.",
|
||||
"commGuidePara037": "<strong>No Parties or Groups should be created for the purpose of attacking any group or individual</strong>. Fight bad habits, not your fellow adventurers!",
|
||||
"commGuideList02P": "<strong>We discourage the sending of unsolicited private messages</strong>. If you receive an unwanted message that makes you uncomfortable or that breaks these Guidelines or the Terms of Service, please block the sender and report it to bring it to Staff attention.",
|
||||
"commGuideList02Q": "<strong>Do not try to get around a block</strong>. If someone has blocked you from sending them private messages, do not contact them elsewhere to ask them to unblock you.",
|
||||
"commGuideList02M": "<strong>Do not ask or beg for Gems, subscriptions, or membership in Group Plans</strong>. If you see or receive unwanted messages asking for paid items, please report them. Repeated Gem or subscription begging, especially after a warning, may result in an account ban.",
|
||||
"commGuideList02L": "<strong>We highly discourage the exchange of personal information--particularly information that can be used to identify you</strong>. Identifying information can include but is not limited to: your address, your email, and your password or API token. If you are asked for personal information in a Party chat or private message, we highly recommend that you do not respond, and alert the Staff by either reporting the message or contacting <a href='mailto:admin@habitica.com' target='_blank'>admin@habitica.com</a> with screenshots of the messages if more context is needed.",
|
||||
"commGuideList02J": "<strong>Do not spam</strong>. Spamming may include, but is not limited to: sending multiple unsolicited private messages, sending nonsensical messages, sending multiple promotional messages about a Party or Challenge, or creating multiple similar or low quality Challenges in a row. Staff has discretion to determine what messages are considered spamming.",
|
||||
"commGuideList02K": "<strong>Do not send links without explanation or context</strong>. If players clicking on a link will result in any benefit to you, you need to disclose that. This applies in messages as well as Challenges.",
|
||||
|
||||
"commGuideHeadingInfractionsEtc": "Infractions, Consequences, and Restoration",
|
||||
"commGuideHeadingInfractions": "Infractions",
|
||||
"commGuidePara050": "Overwhelmingly, Habiticans assist each other, are respectful, and work to make the whole community fun and friendly. However, once in a blue moon, something that a Habitican does may violate one of the above guidelines. When this happens, the Staff will take whatever actions they deem necessary to keep Habitica safe and comfortable for everyone.",
|
||||
"commGuidePara050": "Overwhelmingly, Habiticans assist each other, are respectful, and work to make the atmosphere here fun and friendly. However, once in a blue moon, something that a Habitican does may violate one of the above Guidelines. When this happens, the Staff will take whatever actions they deem necessary to keep Habitica safe and comfortable for everyone.",
|
||||
"commGuidePara051": "<strong>There are a variety of infractions, and they are dealt with depending on their severity</strong>. These are not comprehensive lists, and the Staff can make decisions on topics not covered here at their own discretion. The Staff will take context into account when evaluating infractions.",
|
||||
|
||||
"commGuideHeadingSevereInfractions": "Severe Infractions",
|
||||
"commGuidePara052": "Severe infractions greatly harm the safety of Habitica's community and users, and therefore have severe consequences as a result.",
|
||||
"commGuidePara053": "The following are examples of some severe infractions. This is not a comprehensive list.",
|
||||
"commGuideList05A": "Violation of Terms and Conditions",
|
||||
"commGuideList05B": "Hate Speech/Images, Harassment/Stalking, Cyber-Bullying, Flaming, and Trolling",
|
||||
"commGuideList05C": "Violation of Probation",
|
||||
"commGuideList05D": "Impersonation of Staff - this includes claiming user-created spaces not affiliated with Habitica are official and/or moderated by Habitica or its Staff",
|
||||
"commGuideList05D": "Impersonation of Staff - this includes claiming player-created spaces not affiliated with Habitica are official and/or moderated by Habitica or its Staff",
|
||||
"commGuideList05E": "Repeated Moderate Infractions",
|
||||
"commGuideList05F": "Creation of a duplicate account to avoid consequences (for example, making a new account to chat after having chat privileges revoked)",
|
||||
"commGuideList05F": "Creation of a duplicate account to avoid consequences",
|
||||
"commGuideList05G": "Intentional deception of Staff in order to avoid consequences or to get another user in trouble",
|
||||
"commGuideList05H": "Severe or repeated attempts to defraud or pressure other players for real-money items",
|
||||
"commGuideList05A": "Other breaches of the Terms and Conditions not specified here",
|
||||
|
||||
"commGuideHeadingModerateInfractions": "Moderate Infractions",
|
||||
"commGuidePara054": "Moderate infractions do not make our community unsafe, but they do make it unpleasant. These infractions will have moderate consequences. When in conjunction with multiple infractions, the consequences may grow more severe.",
|
||||
"commGuidePara054": "These infractions will have moderate consequences. When in conjunction with multiple infractions, the consequences may grow more severe.",
|
||||
"commGuidePara055": "The following are some examples of Moderate Infractions. This is not a comprehensive list.",
|
||||
"commGuideList06A": "<strong>Ignoring, disrespecting or arguing with Staff.</strong> This includes publicly complaining about staff or other users, publicly glorifying or defending banned users, or debating whether or not a staff action was appropriate. If you are concerned about one of the rules or the behavior of the Staff, please contact us via email (<a href='mailto:admin@habitica.com' target='_blank'>admin@habitica.com</a>).",
|
||||
"commGuideList06B": "Backseat Modding. To quickly clarify a relevant point: A friendly mention of the rules is fine. Backseat modding consists of telling, demanding, and/or strongly implying that someone must take an action that you describe to correct a mistake. You can alert someone to the fact that they have committed a transgression, but please do not demand an action -- for example, saying, \"Just so you know, profanity is discouraged in the Tavern, so you may want to delete that,\" would be better than saying, \"I'm going to have to ask you to delete that post.\"",
|
||||
"commGuideList06C": "Intentionally flagging innocent posts.",
|
||||
"commGuideList06D": "Repeatedly Violating Public Space Guidelines",
|
||||
"commGuideList06A": "Ignoring, disrespecting or arguing with Staff. If you are concerned about one of the rules or the behavior of the staff, please contact us at <a href='mailto:admin@habitica.com' target='_blank'>admin@habitica.com</a>.",
|
||||
"commGuideList06C": "Intentionally flagging innocent Challenges, profiles, or messages.",
|
||||
"commGuideList06E": "Repeatedly Committing Minor Infractions",
|
||||
|
||||
"commGuideHeadingMinorInfractions": "Minor Infractions",
|
||||
"commGuidePara056": "Minor Infractions, while discouraged, still have minor consequences. If they continue to occur, they can lead to more severe consequences over time.",
|
||||
"commGuidePara056": "Minor Infractions, while discouraged, still have minor consequences. If they continue to occur, they can lead to more severe consequences over time. Minor infractions are typically first time violations of these Guidelines but may include other circumstances.",
|
||||
"commGuidePara057": "The following are some examples of Minor Infractions. This is not a comprehensive list.",
|
||||
"commGuideList07A": "First-time violation of Public Space Guidelines",
|
||||
"commGuideList07B": "Any statements or actions that trigger a \"Please Don't\" from a Staff member. When you are asked not to do something publicly, this in itself can be a consequence. If Staff have to issue many of these corrections to the same person, it may count as a larger infraction",
|
||||
@@ -85,34 +61,26 @@
|
||||
"commGuidePara057A": "Some posts may be hidden because they contain sensitive information or might give people the wrong idea. Typically this does not count as an infraction, particularly not the first time it happens!",
|
||||
|
||||
"commGuideHeadingConsequences": "Consequences",
|
||||
"commGuidePara058": "In Habitica -- as in real life -- every action has a consequence, whether it is getting fit because you've been running, getting cavities because you've been eating too much sugar, or passing a class because you've been studying.",
|
||||
"commGuidePara059": "<strong>Similarly, all infractions have direct consequences.</strong> Some sample consequences are outlined below.",
|
||||
"commGuidePara060": "<strong>If your infraction has a moderate or severe consequence, if appropriate for the circumstances, there will be a post from a staff member in the forum in which the infraction occurred explaining</strong>:",
|
||||
"commGuideList08A": "what your infraction was",
|
||||
"commGuideList08B": "what the consequence is",
|
||||
"commGuideList08C": "what to do to correct the situation and restore your status, if possible.",
|
||||
"commGuidePara060A": "If the situation calls for it, you may receive a PM or email as well as a post in the forum in which the infraction occurred. In some cases you may not be reprimanded in public at all.",
|
||||
"commGuidePara060B": "If your account is banned (a severe consequence), you will not be able to log into Habitica and will receive an error message upon attempting to log in. <strong>If you wish to apologize or make a plea for reinstatement, please email the staff at <a href='mailto:admin@habitica.com' target='_blank'>admin@habitica.com</a> with your User ID</strong> (which will be given in the error message) or @username. It is <strong>your</strong> responsibility to reach out if you desire reconsideration or reinstatement.",
|
||||
"commGuidePara059": "<strong>Community infractions have direct consequences.</strong> Some sample consequences are outlined below.",
|
||||
"commGuideHeadingSevereConsequences": "Examples of Severe Consequences",
|
||||
"commGuideList09A": "Account bans (see above)",
|
||||
"commGuideList09C": "Permanently disabling (\"freezing\") progression through Contributor Tiers",
|
||||
"commGuideList09A": "Account bans",
|
||||
"commGuideList09C": "Permanently stopping progression through Contributor Tiers",
|
||||
"commGuideList09D": "Removal or demotion of Contributor Tiers",
|
||||
"commGuideList09E": "Permanent removal of ability to send private messages or appear in Party member search",
|
||||
"commGuideHeadingModerateConsequences": "Examples of Moderate Consequences",
|
||||
"commGuideList10A": "Restricted public and/or private chat privileges",
|
||||
"commGuideList10A1": "If your actions result in revocation of your chat privileges, you must email <a href='mailto:admin@habitica.com' target='_blank'>admin@habitica.com</a>. You may be reinstated at staff discretion if you comply politely with the actions required and agree to abide by the Community Guidelines and ToS",
|
||||
"commGuideList10D": "Temporarily disabling (\"freezing\") progression through Contributor Tiers",
|
||||
"commGuideList10F": "Putting users on \"Probation\"",
|
||||
"commGuideList10D": "Temporarily pausing progression through Contributor Tiers",
|
||||
"commGuideList10G": "Temporary disabling of ability to send private messages or appear in Party member search",
|
||||
"commGuideHeadingMinorConsequences": "Examples of Minor Consequences",
|
||||
"commGuideList11A": "Reminders of Public Space Guidelines",
|
||||
"commGuideList11A": "Reminders of Guidelines",
|
||||
"commGuideList11B": "Warnings",
|
||||
"commGuideList11C": "Requests",
|
||||
"commGuideList11D": "Deletions (Staff may delete problematic content)",
|
||||
"commGuideList11E": "Edits (Staff may edit problematic content)",
|
||||
"commGuideList11D": "Deletion of problematic content by Staff",
|
||||
"commGuideList11E": "Edits of problematic content by Staff",
|
||||
|
||||
"commGuideHeadingRestoration": "Restoration",
|
||||
"commGuidePara061": "Habitica is a land devoted to self-improvement, and we believe in second chances. <strong>If you commit an infraction and receive a consequence, view it as a chance to evaluate your actions and strive to be a better member of the community</strong>.",
|
||||
"commGuidePara062": "The announcement, message, and/or email that you receive explaining the consequences of your actions is a good source of information. Cooperate with any restrictions which have been imposed, and endeavor to meet the requirements to have any penalties lifted.",
|
||||
"commGuidePara063": "If you do not understand your consequences, or the nature of your infraction, ask the Staff for help so you can avoid committing infractions in the future. If you feel a particular decision was unfair, you can contact the staff to discuss it at <a href='mailto:admin@habitica.com' target='_blank'>admin@habitica.com</a>.",
|
||||
"commGuidePara061": "Habitica is devoted to self-improvement, and we believe in second chances. <strong>If you commit an infraction and receive a consequence, view it as a chance to evaluate your actions and strive to be a better member of the community</strong>.",
|
||||
"commGuidePara062": "<strong>If you wish to ask questions about your infraction or consequences, apologize, or make a plea for reinstatement, please contact us at <a href='mailto:admin@habitica.com' target='_blank'>admin@habitica.com</a> with your User ID or @username</strong>. It is <strong>your</strong> responsibility to reach out.",
|
||||
"commGuidePara063": "If you do not understand your consequences or the nature of your infraction, or if you have other questions related to the matter, you can contact the staff to discuss it at <a href='mailto:admin@habitica.com' target='_blank'>admin@habitica.com</a>. Cooperate with any restrictions which have been imposed, and endeavor to meet the requirements to have any penalties lifted.",
|
||||
|
||||
"commGuideHeadingMeet": "Meet the Staff",
|
||||
"commGuidePara007": "The Habitica Staff keep the app and sites running and can act as chat moderators. They have purple tags marked with crowns. Their title is \"Heroic\".",
|
||||
@@ -122,20 +90,17 @@
|
||||
"commGuidePara011b": "on GitHub/Fandom",
|
||||
"commGuidePara011c": "on the Wiki",
|
||||
"commGuidePara011d": "on GitHub",
|
||||
"commGuidePara013": "In a community as big as Habitica, users come and go, and sometimes a staff member or moderator needs to lay down their noble mantle and relax. The following are Staff and Moderators Emeritus. They no longer act with the power of a Staff member or Moderator, but we would still like to honor their work!",
|
||||
"commGuidePara013": "In a community as big as Habitica, players come and go, and sometimes a Staff member or moderator needs to lay down their noble mantle and relax. The following are Staff and Moderators Emeritus. They no longer act with the power of a Staff member or Moderator, but we would still like to honor their work!",
|
||||
"commGuidePara014": "Staff and Moderators Emeritus:",
|
||||
|
||||
"commGuideHeadingFinal": "The Final Section",
|
||||
"commGuidePara067": "So there you have it, brave Habitican -- the Community Guidelines! Wipe that sweat off of your brow and give yourself some XP for reading it all. If you have any questions or concerns about these Community Guidelines, please reach out to us via <a href='mailto:admin@habitica.com' target='_blank'>admin@habitica.com</a> and we will be happy to help clarify things.",
|
||||
"commGuidePara067": "So there you have it, brave Habitican -- the Community Guidelines! Wipe that sweat off of your brow and give yourself some EXP for reading it all. If you have any questions or concerns about these Community Guidelines, please reach out to us via <a href='mailto:admin@habitica.com' target='_blank'>admin@habitica.com</a> and we will be happy to help clarify things.",
|
||||
"commGuidePara068": "Now go forth, brave adventurer, and slay some Dailies!",
|
||||
|
||||
"commGuideHeadingLinks": "Useful Links",
|
||||
"commGuideLink01": "<a href='/groups/guild/5481ccf3-5d2d-48a9-a871-70a7380cee5a' target='_blank'>Habitica Help: Ask a Question</a>: a Guild for users to ask questions!",
|
||||
"commGuideLink02": "<a href='https://habitica.fandom.com/wiki/Habitica_Wiki' target='_blank'>The Wiki</a>: the biggest collection of information about Habitica.",
|
||||
"commGuideLink02": "<a href='https://habitica.fandom.com/wiki/Habitica_Wiki' target='_blank'>The Wiki</a>: the biggest collection of information about Habitica. Note that this space is unofficial, being hosted by Fandom and maintained by players.",
|
||||
"commGuideLink03": "<a href='https://github.com/HabitRPG/habitica' target='_blank'>GitHub</a>: for helping with code!",
|
||||
"commGuideLink04": "<a href='https://docs.google.com/forms/d/e/1FAIpQLScPhrwq_7P1C6PTrI3lbvTsvqGyTNnGzp1ugi1Ml0PFee_p5g/viewform?usp=sf_link' target='_blank'>The Feedback Form</a>: for site and app feature requests.",
|
||||
"commGuideLink06": "<a href='https://trello.com/b/vwuE9fbO/' target='_blank'>The Art Trello</a>: for submitting pixel art.",
|
||||
"commGuideLink07": "<a href='https://trello.com/b/nnv4QIRX/' target='_blank'>The Quest Trello</a>: for submitting quest writing.",
|
||||
|
||||
"commGuidePara069": "The following talented artists contributed to these illustrations:"
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
"contribModal": "<%= name %>, you awesome person! You're now a tier <%= level %> contributor for helping Habitica.",
|
||||
"contribLink": "See what prizes you've earned for your contribution!",
|
||||
"contribName": "Contributor",
|
||||
"contribText": "Has contributed to Habitica, whether via code, art, music, writing, or other methods. To learn more, join the Aspiring Legends Guild!",
|
||||
"contribText": "Has contributed to Habitica, whether via code, art, music, writing, or other methods.",
|
||||
"kickstartName": "Kickstarter Backer - $<%= key %> Tier",
|
||||
"kickstartText": "Backed the Kickstarter Project",
|
||||
"helped": "Helped Habitica Grow",
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
"marketing1Lead2": "Improve your habits to build up your avatar. Show off the sweet gear you've earned!",
|
||||
"marketing1Lead3Title": "Find Random Prizes",
|
||||
"marketing1Lead3": "For some, it's the gamble that motivates them: a system called \"stochastic rewards.\" Habitica accommodates all reinforcement and punishment styles: positive, negative, predictable, and random.",
|
||||
"marketing2Header": "Compete With Friends, Join Interest Groups",
|
||||
"marketing2Header": "Compete with Friends",
|
||||
"marketing2Lead1Title": "Social Productivity",
|
||||
"marketing2Lead1": "While you can play Habitica solo, the lights really turn on when you start collaborating, competing, and holding each other accountable. The most effective part of any self-improvement program is social accountability, and what better an environment for accountability and competition than a video game?",
|
||||
"marketing2Lead2Title": "Fight Monsters",
|
||||
@@ -67,7 +67,7 @@
|
||||
"pkQuestion2": "Why does Habitica work?",
|
||||
"pkAnswer2": "Forming a new habit is hard because people really need that obvious, instant reward. For example, it’s tough to start flossing, because even though our dentist tells us that it's healthier in the long run, in the immediate moment it just makes your gums hurt. <br /> Habitica's gamification adds a sense of instant gratification to everyday objectives by rewarding a tough task with experience, gold… and maybe even a random prize, like a dragon egg! This helps keep people motivated even when the task itself doesn't have an intrinsic reward, and we've seen people turn their lives around as a result.",
|
||||
"pkQuestion3": "Why did you add social features?",
|
||||
"pkAnswer3": "Social pressure is a huge motivating factor for a lot of people, so we knew that we wanted to have a strong community that would hold each other accountable for their goals and cheer for their successes. Luckily, one of the things that multiplayer video games do best is foster a sense of community among their users! Habitica’s community structure borrows from these types of games; you can form a small Party of close friends, but you can also join a larger, shared-interest groups known as a Guild. Although some users choose to play solo, most decide to form a support network that encourages social accountability through features such as Quests, where Party members pool their productivity to battle monsters together.",
|
||||
"pkAnswer3": "Social pressure is a huge motivating factor for a lot of people, so we knew that we wanted to have a strong community that would hold each other accountable for their goals and cheer for their successes. Luckily, one of the things that multiplayer video games do best is foster a sense of community among their users! Habitica’s community structure borrows from these types of games. Although some users choose to play solo, most decide to form a support network in a small Party of close friends that encourages social accountability through features such as Quests, where Party members pool their productivity to battle monsters together.",
|
||||
"pkQuestion4": "Why does skipping tasks remove your avatar’s health?",
|
||||
"pkAnswer4": "If you skip one of your daily goals, your avatar will lose health the following day. This serves as an important motivating factor to encourage people to follow through with their goals because people really hate hurting their little avatar! Plus, the social accountability is critical for a lot of people: if you’re fighting a monster with your friends, skipping your tasks hurts their avatars, too.",
|
||||
"pkQuestion5": "What distinguishes Habitica from other gamification programs?",
|
||||
|
||||
@@ -707,6 +707,10 @@
|
||||
"weaponArmoireFinelyCutGemNotes": "What a find! This stunning, precision-cut gem will be the prize of your collection. And it might contain some special magic, just waiting for you to tap into it. Increases Constitution by <%= con %>. Enchanted Armoire: Jeweler Set (Item 4 of 4).",
|
||||
"weaponArmoirePaintbrushText": "Paintbrush",
|
||||
"weaponArmoirePaintbrushNotes": "A jolt of pure inspiration rushes through you when you pick up this brush, allowing you to paint anything you can imagine. Increases Intelligence by <%= int %>. Enchanted Armoire: Painter Set (Item 3 of 4).",
|
||||
"weaponArmoireMopText": "Mop",
|
||||
"weaponArmoireMopNotes": "Step 1: Dunk the mop in a bucket of water and suds. Step 2: Drag the mop along the floor. Step 3: Pretend the end of the mop handle is a microphone and sing your heart out. Step 4: Repeat Steps 1-3 until floor is clean. Increases Constitution and Perception by <%= attrs %> each. Enchanted Armoire: Cleaning Supplies Set Two (Item 2 of 3)",
|
||||
"weaponArmoireCleaningClothText": "Cleaning Cloth",
|
||||
"weaponArmoireCleaningClothNotes": "Take this tidying tool on your adventures and always be able to polish a pretty plaque or wipe a wooden windowsill. Increases Strength and Constitution by <%= attrs %> each. Enchanted Armoire: Cleaning Supplies Set Two (Item 3 of 3)",
|
||||
|
||||
"armor": "armor",
|
||||
"armorCapitalized": "Armor",
|
||||
@@ -796,6 +800,8 @@
|
||||
"armorSpecialTurkeyArmorGildedNotes": "Strut your stuff in this seasonally shiny armor! Confers no benefit.",
|
||||
"armorSpecialKS2019Text": "Mythic Gryphon Armor",
|
||||
"armorSpecialKS2019Notes": "Glowing from within like a gryphon's noble heart, this resplendent armor encourages you to take pride in your accomplishments. Increases Constitution by <%= con %>.",
|
||||
"armorSpecialHeroicTunicText": "Heroic Tunic",
|
||||
"armorSpecialHeroicTunicNotes": "They say heroes shouldn't rest on their laurels but you can rest in this comfortable and fashionable raiment. Increases all stats by <%= attrs %>.",
|
||||
|
||||
"armorSpecialYetiText": "Yeti-Tamer Robe",
|
||||
"armorSpecialYetiNotes": "Fuzzy and fierce. Increases Constitution by <%= con %>. Limited Edition 2013-2014 Winter Gear.",
|
||||
@@ -1487,6 +1493,8 @@
|
||||
"armorArmoireDiagonalRainbowShirtNotes": "A splash of color with a dash of style. Be joyful! Increases Constitution and Perception by <%= attrs %> each. Enchanted Armoire: Rainbow Set (Item 2 of 2).",
|
||||
"armorArmoireAdmiralsUniformText": "Admiral's Uniform",
|
||||
"armorArmoireAdmiralsUniformNotes": "We salute you! This naval uniform signals that you’re ready to take command of your tasks as well as a ship. Increases Constitution and Strength by <%= attrs %> each. Enchanted Armoire: Admiral’s Set (Item 2 of 2).",
|
||||
"armorArmoireKarateGiText": "Karate Gi",
|
||||
"armorArmoireKarateGiNotes": "This lightweight karate uniform is perfect for practice or competition. Increases Strength by <%= str %>. Enchanted Armoire: Karate Set (Item 1 of 10).",
|
||||
|
||||
"headgear": "helm",
|
||||
"headgearCapitalized": "Headgear",
|
||||
@@ -2703,6 +2711,8 @@
|
||||
"shieldArmoireBasketballNotes": "Swish! Whenever you shoot this magic basketball, there will be nothing but net. Increases Constitution and Strength by <%= attrs %> each. Enchanted Armoire: Old Timey Basketball Set (Item 2 of 2).",
|
||||
"shieldArmoirePaintersPaletteText": "Painter's Palette",
|
||||
"shieldArmoirePaintersPaletteNotes": "Paints in all colors of the rainbow are at your disposal. Is it magic that makes them so vivid when you use them, or is it your talent? Increases Strength by <%= str %>. Enchanted Armoire: Painter Set (Item 4 of 4).",
|
||||
"shieldArmoireBucketText": "Bucket",
|
||||
"shieldArmoireBucketNotes": "Though this bucket is helpful in holding a mixture of water and cleaning solution, you could also use it to collect, carry, and cart around just about anything that fits inside! Increases Strength and Intelligence by <%= attrs %> each. Enchanted Armoire: Cleaning Supplies Set 2 (Item 1 of 3)",
|
||||
|
||||
"back": "Back Accessory",
|
||||
"backBase0Text": "No Back Accessory",
|
||||
@@ -2775,6 +2785,8 @@
|
||||
"backMystery202302Notes": "Anytime you wear this tail it's sure to be a frabjous day! Callooh! Callay! Confers no benefit. February 2023 Subscriber Item.",
|
||||
"backMystery202305Text": "Eventide Wings",
|
||||
"backMystery202305Notes": "Catch the sparkle of the evening star and soar to strange realms on these wings. Confers no benefit. May 2023 Subscriber Item.",
|
||||
"backMystery202309Text": "Colossal Comet Moth Wings",
|
||||
"backMystery202309Notes": "Flutter across forests, glide over mountains, and soar over oceans on these bright and beautiful wings. Confers no benefit. September 2023 Subscriber Item.",
|
||||
|
||||
"backSpecialWonderconRedText": "Mighty Cape",
|
||||
"backSpecialWonderconRedNotes": "Swishes with strength and beauty. Confers no benefit. Special Edition Convention Item.",
|
||||
@@ -2794,6 +2806,8 @@
|
||||
"backSpecialNamingDay2020Notes": "Happy Naming Day! Swish this fiery, pixely tail about as you celebrate Habitica. Confers no benefit.",
|
||||
"backSpecialAnniversaryText": "Habitica Hero Cape",
|
||||
"backSpecialAnniversaryNotes": "Let this proud cape fly in the wind and tell everyone that you're a Habitica Hero. Confers no benefit. Special Edition 10th Birthday Bash Item.",
|
||||
"backSpecialHeroicAureoleText": "Heroic Aureole",
|
||||
"backSpecialHeroicAureoleNotes": "The gems on this aureole glimmer when you tell your tales of glory. Increases all stats by <%= attrs %>.",
|
||||
|
||||
"backBearTailText": "Bear Tail",
|
||||
"backBearTailNotes": "This tail makes you look like a brave bear! Confers no benefit.",
|
||||
@@ -2869,6 +2883,24 @@
|
||||
"bodyArmoireLifeguardWhistleNotes": "Call that misbehaving habit to order! It should know the rules! Increases Intelligence by <%= int %>. Enchanted Armoire: Lifeguard Set (Item 3 of 3).",
|
||||
"bodyArmoireClownsBowtieText": "Clown's Bow-Tie",
|
||||
"bodyArmoireClownsBowtieNotes": "A nice bow-tie is no joking matter, even for a clown. Increases Strength, Intelligence, Constitution and Perception by <%= attrs %> each. Enchanted Armoire: Clown Set (Item 5 of 5).",
|
||||
"bodyArmoireKarateWhiteBeltText": "White Belt",
|
||||
"bodyArmoireKarateWhiteBeltNotes": "This lowest level belt is for those who are just beginning their journey. Increases Intelligence by <%= int %>. Enchanted Armoire: Karate Set (Item 2 of 10).",
|
||||
"bodyArmoireKarateYellowBeltText": "Yellow Belt",
|
||||
"bodyArmoireKarateYellowBeltNotes": "This belt is for beginners who have learned the basics. Increases Perception by <%= per %>. Enchanted Armoire: Karate Set (Item 3 of 10).",
|
||||
"bodyArmoireKarateOrangeBeltText": "Orange Belt",
|
||||
"bodyArmoireKarateOrangeBeltNotes": "This belt is for those who have grown and mastered the beginner level. Increases Constitution by <%= con %>. Enchanted Armoire: Karate Set (Item 4 of 10).",
|
||||
"bodyArmoireKarateGreenBeltText": "Green Belt",
|
||||
"bodyArmoireKarateGreenBeltNotes": "This belt is for those at the intermediate level learning to strengthen their skills. Increases Strength by <%= str %>. Enchanted Armoire: Karate Set (Item 5 of 10).",
|
||||
"bodyArmoireKarateBlueBeltText": "Blue Belt",
|
||||
"bodyArmoireKarateBlueBeltNotes": "This belt is for those who are learning more and developing their minds and bodies. Increases Constitution by <%= con %>. Enchanted Armoire: Karate Set (Item 6 of 10).",
|
||||
"bodyArmoireKaratePurpleBeltText": "Purple Belt",
|
||||
"bodyArmoireKaratePurpleBeltNotes": "This belt is for those ready to embark toward advanced study. Increases Constitution by <%= con %>. Enchanted Armoire: Karate Set (Item 7 of 10).",
|
||||
"bodyArmoireKarateRedBeltText": "Red Belt",
|
||||
"bodyArmoireKarateRedBeltNotes": "This belt is for those who have learned to be cautious in their practice. Increases Perception by <%= per %>. Enchanted Armoire: Karate Set (Item 8 of 10).",
|
||||
"bodyArmoireKarateBrownBeltText": "Brown Belt",
|
||||
"bodyArmoireKarateBrownBeltNotes": "This belt is for those whose techniques and skills have matured. Increases Strength by <%= str %>. Enchanted Armoire: Karate Set (Item 9 of 10).",
|
||||
"bodyArmoireKarateBlackBeltText": "Black Belt",
|
||||
"bodyArmoireKarateBlackBeltNotes": "This highest level belt is for those who seek a deeper understanding and can pass their knowledge on to others. Increases Intelligence by <%= int %>. Enchanted Armoire: Karate Set (Item 10 of 10).",
|
||||
|
||||
"headAccessory": "Head Accessory",
|
||||
"accessories": "Accessories",
|
||||
@@ -2876,6 +2908,8 @@
|
||||
|
||||
"headAccessoryBase0Text": "No Head Accessory",
|
||||
"headAccessoryBase0Notes": "No Head Accessory.",
|
||||
"headAccessorySpecialHeroicCircletText": "Heroic Circlet",
|
||||
"headAccessorySpecialHeroicCircletNotes": "Heavy is the head that wears the crown, but this circlet is as light as your generous spirit. Increases all stats by <%= attrs %>.",
|
||||
|
||||
"headAccessorySpecialSpringRogueText": "Purple Cat Ears",
|
||||
"headAccessorySpecialSpringRogueNotes": "These feline ears twitch to detect incoming threats. Confers no benefit. Limited Edition 2014 Spring Gear.",
|
||||
@@ -2990,6 +3024,8 @@
|
||||
"headAccessoryMystery202305Notes": "These horns glow with reflected moonlight. Confers no benefit. May 2023 Subscriber Item.",
|
||||
"headAccessoryMystery202307Text": "Kraken's Crown",
|
||||
"headAccessoryMystery202307Notes": "This mighty circlet summons cyclones and stormy weather! Confers no benefit. July 2023 Subscriber Item.",
|
||||
"headAccessoryMystery202309Text": "Colossal Comet Moth Antennae",
|
||||
"headAccessoryMystery202309Notes": "These antennae are fashionable and feathery, but also help you navigate! Confers no benefit. September 2023 Subscriber Item.",
|
||||
|
||||
"headAccessoryMystery301405Text": "Headwear Goggles",
|
||||
"headAccessoryMystery301405Notes": "\"Goggles are for your eyes,\" they said. \"Nobody wants goggles that you can only wear on your head,\" they said. Hah! You sure showed them! Confers no benefit. August 3015 Subscriber Item.",
|
||||
|
||||
@@ -101,8 +101,8 @@
|
||||
"reportDescriptionText": "Include screenshots or Javascript console errors if helpful.",
|
||||
"reportDescriptionPlaceholder": "Describe the bug in detail here",
|
||||
"submitBugReport": "Submit Bug Report",
|
||||
"reportSent": "Bug report sent!",
|
||||
"reportSentDescription": "We’ll get back to you once our team has a chance to investigate. Thank you for reporting the issue.",
|
||||
"reportSent": "Thank you for your submission!",
|
||||
"reportSentDescription": "We’ll get back to you once our team has a chance to review.",
|
||||
"overview": "Overview for New Users",
|
||||
"dateFormat": "Date Format",
|
||||
"achievementStressbeast": "Savior of Stoïkalm",
|
||||
@@ -216,5 +216,11 @@
|
||||
"refreshList": "Refresh List",
|
||||
"leaveHabitica": "You are about to leave Habitica.com",
|
||||
"leaveHabiticaText": "Habitica is not responsible for the content of any linked website that is not owned or operated by HabitRPG.<br>Please note that these websites' practices may differ from Habitica’s community guidelines.",
|
||||
"skipExternalLinkModal": "Hold CTRL (Windows) or Command (Mac) when clicking a link to skip this modal."
|
||||
"skipExternalLinkModal": "Hold CTRL (Windows) or Command (Mac) when clicking a link to skip this modal.",
|
||||
"askQuestionHeaderDescribe": "New to Habitica and don't know what you're doing? Veteran but just can't figure out how to use one of the features? Fill out this form and our team will get back to you.",
|
||||
"questionEmailText": "This will only be used to contact you regarding your question.",
|
||||
"question": "Question",
|
||||
"questionDescriptionText": "It's okay to ask your questions in your primary language if you aren't comfortable speaking in English.",
|
||||
"questionPlaceholder": "Ask your question here",
|
||||
"submitQuestion": "Submit Question"
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"tavern": "Tavern Chat",
|
||||
"tavernChat": "Tavern Chat",
|
||||
"innCheckOutBanner": "You are currently checked into the Inn. Your Dailies won't damage you and you won't make progress towards Quests.",
|
||||
"innCheckOutBannerShort": "You are checked into the Inn.",
|
||||
"innCheckOutBanner": "You have currently paused damage. Your Dailies won't damage you and you won't make progress towards Quests.",
|
||||
"innCheckOutBannerShort": "You have paused damage.",
|
||||
"resumeDamage": "Resume Damage",
|
||||
"helpfulLinks": "Helpful Links",
|
||||
"lookingForGroup": "Looking for Group (Party Wanted) Posts",
|
||||
@@ -26,8 +26,8 @@
|
||||
"leave": "Leave",
|
||||
"invitedToParty": "You were invited to join the Party <span class=\"notification-bold\"><%- party %></span>",
|
||||
"invitedToPartyBy": "<a href=\"/profile/<%- userId %>\" target=\"_blank\">@<%- userName %></a> has invited you to join the Party <span class=\"notification-bold\"><%- party %></span>",
|
||||
"invitedToPrivateGuild": "You were invited to join the private Guild <span class=\"notification-bold\"><%- guild %></span>",
|
||||
"invitedToPublicGuild": "You were invited to join the Guild <span class=\"notification-bold-blue\"><%- guild %></span>",
|
||||
"invitedToPrivateGuild": "You were invited to join the private Group <span class=\"notification-bold\"><%- guild %></span>",
|
||||
"invitedToPublicGuild": "You were invited to join the Group <span class=\"notification-bold-blue\"><%- guild %></span>",
|
||||
"invitationAcceptedHeader": "Your Invitation has been Accepted",
|
||||
"invitationAcceptedBody": "<%= username %> accepted your invitation to <%= groupName %>!",
|
||||
"systemMessage": "System Message",
|
||||
@@ -49,11 +49,10 @@
|
||||
"inviteOnly": "Invite Only",
|
||||
"gemCost": "The Gem cost promotes high quality Guilds, and is transferred into your Guild's bank for use as prizes in Guild Challenges!",
|
||||
"search": "Search",
|
||||
"publicGuilds": "Public Guilds",
|
||||
"createGuild": "Create Guild",
|
||||
"guild": "Guild",
|
||||
"guilds": "Guilds",
|
||||
"sureKick": "Do you really want to remove this member from the Party/Guild?",
|
||||
"sureKick": "Do you really want to remove this member from the Party or Group?",
|
||||
"optionalMessage": "Optional message",
|
||||
"yesRemove": "Yes, remove them",
|
||||
"sortBackground": "Sort by Background",
|
||||
@@ -246,17 +245,17 @@
|
||||
"newGuildPlaceholder": "Enter your guild's name.",
|
||||
"newPartyPlaceholder": "Enter your party's name.",
|
||||
"guildBank": "Guild Bank",
|
||||
"chatPlaceholder": "Type your message to Guild members here",
|
||||
"chatPlaceholder": "Type your message to Group members here",
|
||||
"partyChatPlaceholder": "Type your message to Party members here",
|
||||
"fetchRecentMessages": "Fetch Recent Messages",
|
||||
"like": "Like",
|
||||
"liked": "Liked",
|
||||
"inviteToGuild": "Invite to Guild",
|
||||
"inviteToGuild": "Invite to Group",
|
||||
"inviteToParty": "Invite to Party",
|
||||
"inviteEmailUsername": "Invite via Email or Username",
|
||||
"inviteEmailUsernameInfo": "Invite users via a valid email or username. If an email isn't registered yet, we'll invite them to join.",
|
||||
"emailOrUsernameInvite": "Email address or username",
|
||||
"messageGuildLeader": "Message Guild Leader",
|
||||
"messageGuildLeader": "Message Group Leader",
|
||||
"messagePartyLeader": "Message Party Leader",
|
||||
"donateGems": "Donate Gems",
|
||||
"updateGuild": "Update Guild",
|
||||
@@ -278,7 +277,7 @@
|
||||
"privateGuild": "Private Guild",
|
||||
"languageSettings": "Language Settings",
|
||||
"bannedWordsAllowed": "Allow banned words",
|
||||
"bannedWordsAllowedDetail": "With this option selected, the use of banned words in this guild will be allowed.",
|
||||
"bannedWordsAllowedDetail": "With this option selected, the use of banned words in this group will be allowed.",
|
||||
"charactersRemaining": "<%= characters %> characters remaining",
|
||||
"guildSummary": "Summary",
|
||||
"guildSummaryPlaceholder": "Write a short description advertising your Guild to other Habiticans. What is the main purpose of your Guild and why should people join it? Try to include useful keywords in the summary so that Habiticans can easily find it when they search!",
|
||||
@@ -318,7 +317,7 @@
|
||||
"viewDetails": "View Details",
|
||||
"participantDesc": "Once all members have either accepted or declined, the Quest begins. Only those who clicked 'accept' will be able to participate in the Quest and receive the rewards.",
|
||||
"groupGems": "Group Gems",
|
||||
"groupGemsDesc": "Guild Gems can be spent to make Challenges! In the future, you will be able to add more Guild Gems.",
|
||||
"groupGemsDesc": "Group Gems can be spent to make Challenges! In the future, you will be able to add more Group Gems.",
|
||||
"groupTaskBoard": "Task Board",
|
||||
"groupInformation": "Group Information",
|
||||
"groupBilling": "Group Billing",
|
||||
@@ -418,5 +417,8 @@
|
||||
"findMorePartyMembers": "Find More Members",
|
||||
"findPartyMembers": "Find Party Members",
|
||||
"noOneLooking": "There’s no one looking for a Party right now.<br>You can check back later!",
|
||||
"tavernDiscontinued": "The Tavern and Guilds have been discontinued",
|
||||
"tavernDiscontinuedDetail": "Due to a number of factors, including changes in how our player base interacts with Habitica, the resources necessary to maintain these spaces became disproportionate to the number of people participating in them and unsustainable over the long term.",
|
||||
"tavernDiscontinuedLinks": "Read more about the <a href='/static/faq/tavern-and-guilds'>Tavern and Guild Service Discontinuation</a> or head back to the <a href='/'>homepage</a>.",
|
||||
"chatSunsetWarning": "⚠️ <strong>Habitica Guilds and Tavern chat will be discontinued on 8/8/2023.</strong> <a href='/static/faq/tavern-and-guilds'>Click here</a> to read more about this change."
|
||||
}
|
||||
|
||||
@@ -46,7 +46,6 @@
|
||||
"messageGroupChatNotFound": "Message not found!",
|
||||
"messageGroupChatAdminClearFlagCount": "Only an admin can clear the flag count!",
|
||||
"messageCannotFlagSystemMessages": "You cannot report a system message. If you need to report a violation of the Community Guidelines related to this message, please email a screenshot and explanation to our Community Manager at <%= communityManagerEmail %>.",
|
||||
"messageGroupChatSpam": "Whoops, looks like you're posting too many messages! Please wait a minute and try again. The Tavern chat only holds 200 messages at a time, so Habitica encourages posting longer, more thoughtful messages and consolidating replies. Can't wait to hear what you have to say. :)",
|
||||
"messageCannotLeaveWhileQuesting": "You cannot accept this party invitation while you are in a quest. If you'd like to join this party, you must first abort your quest, which you can do from your party screen. You will be given back the quest scroll.",
|
||||
"messageUserOperationProtected": "path `<%= operation %>` was not saved, as it's a protected path.",
|
||||
"messageNotificationNotFound": "Notification not found.",
|
||||
@@ -59,5 +58,6 @@
|
||||
"messageMissingDisplayName": "Missing display name.",
|
||||
"reportedMessage": "You have reported this message to moderators.",
|
||||
"canDeleteNow": "You can now delete the message if you wish.",
|
||||
"newsPostNotFound": "News Post not found or you don't have access."
|
||||
"newsPostNotFound": "News Post not found or you don't have access.",
|
||||
"featureRetired": "This feature is no longer supported."
|
||||
}
|
||||
|
||||
@@ -16,14 +16,14 @@
|
||||
"mattBoch": "Matt Boch",
|
||||
"mattBochText1": "Welcome to the Stable! I’m Matt, the beastmaster. Every time you complete a task, you'll have a random chance at receiving an Egg or a Hatching Potion to hatch Pets. When you hatch a Pet, it will appear here! Click a Pet's image to add it to your Avatar. Feed them with the Pet Food you find, and they'll grow into hardy Mounts.",
|
||||
"welcomeToTavern": "Welcome to The Tavern!",
|
||||
"sleepDescription": "Need a break? Check into Daniel's Inn to pause some of Habitica's more difficult game mechanics:",
|
||||
"sleepDescription": "Need a break? Pause Damage (located in Settings) to pause some of Habitica's more difficult game mechanics:",
|
||||
"sleepBullet1": "Your missed Dailies won't damage you (bosses will still do damage caused by other Party member's missed Dailies)",
|
||||
"sleepBullet2": "Your Task streaks and Habit counters will not reset",
|
||||
"sleepBullet3": "Your damage to the Quest boss or found collection items will remain pending until you check out of the Inn",
|
||||
"sleepBullet3": "Your damage to the Quest boss or found collection items will remain pending until you resume Damage",
|
||||
"pauseDailies": "Pause Damage",
|
||||
"unpauseDailies": "Unpause Damage",
|
||||
"staffAndModerators": "Staff and Moderators",
|
||||
"communityGuidelinesIntro": "Habitica tries to create a welcoming environment for users of all ages and backgrounds, especially in public spaces like the Tavern. If you have any questions, please consult our <a href='/static/community-guidelines' target='_blank'>Community Guidelines</a>.",
|
||||
"communityGuidelinesIntro": "Habitica tries to create a welcoming environment for users of all ages and backgrounds, especially in spaces like Groups and Parties. If you have any questions, please consult our <a href='/static/community-guidelines' target='_blank'>Community Guidelines</a>.",
|
||||
"acceptCommunityGuidelines": "I agree to follow the Community Guidelines",
|
||||
"worldBossEvent": "World Boss Event",
|
||||
"worldBossDescription": "World Boss Description",
|
||||
@@ -110,7 +110,6 @@
|
||||
"tourStatsPage": "This is your Stats page! Earn achievements by completing the listed tasks.",
|
||||
"tourTavernPage": "Welcome to the Tavern, an all-ages chat room! You can keep your Dailies from hurting you in case of illness or travel by clicking \"Pause Damage\". Come say hi!",
|
||||
"tourPartyPage": "Welcome to your new Party! You can invite other players to your Party by username, email, or from a list of players looking for a Party to earn the exclusive Basi-List Quest Scroll.<br/><br/>Select <a href='/static/faq#parties'>FAQ</a> from the Help dropdown to learn more about how Parties work.",
|
||||
"tourGuildsPage": "Guilds are common-interest chat groups created by the players, for the players. Browse through the list and join the Guilds that interest you. Be sure to check out the popular Habitica Help: Ask a Question guild, where anyone can ask questions about Habitica!",
|
||||
"tourChallengesPage": "Challenges are themed task lists created by users! Joining a Challenge will add its tasks to your account. Compete against other users to win Gem prizes!",
|
||||
"tourMarketPage": "Every time you complete a task, you'll have a random chance at receiving an Egg, a Hatching Potion, or a piece of Pet Food. You can also buy these items here.",
|
||||
"tourHallPage": "Welcome to the Hall of Heroes, where open-source contributors to Habitica are honored. Whether through code, art, music, writing, or even just helpfulness, they have earned Gems, exclusive equipment, and prestigious titles. You can contribute to Habitica, too!",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user