mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-18 23:27:26 +01:00
Compare commits
396 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
faa7ff6328 | ||
|
|
50cc7ee09a | ||
|
|
db56134832 | ||
|
|
1ea954ab10 | ||
|
|
8f64afe9df | ||
|
|
bf0e640fa6 | ||
|
|
a6792a4f08 | ||
|
|
0007736f5c | ||
|
|
d564944507 | ||
|
|
b679cfb935 | ||
|
|
464e4f10b2 | ||
|
|
ac4e6490d9 | ||
|
|
a3784e98a3 | ||
|
|
5931f02692 | ||
|
|
e21aa074e4 | ||
|
|
fd038bd150 | ||
|
|
bb7e0e22ec | ||
|
|
a79a088a8f | ||
|
|
60c0e6b3df | ||
|
|
43c10f75c3 | ||
|
|
d4e3e83d46 | ||
|
|
013f8bcca7 | ||
|
|
ebd0cb72de | ||
|
|
c44b1670cf | ||
|
|
39477c6f11 | ||
|
|
1371b80635 | ||
|
|
9fa355fbcc | ||
|
|
a8b52ab656 | ||
|
|
cce6d91611 | ||
|
|
3109a03055 | ||
|
|
14518b8213 | ||
|
|
3ad31c7cd0 | ||
|
|
bfa6d24e47 | ||
|
|
8c88f56d08 | ||
|
|
1df3f9d9f3 | ||
|
|
b5a0dad7f7 | ||
|
|
9b34c3e11a | ||
|
|
9d61bd724a | ||
|
|
13c21139dd | ||
|
|
8e85de53cb | ||
|
|
bf222351e5 | ||
|
|
6867aab74b | ||
|
|
0cae808b7e | ||
|
|
81be8316a0 | ||
|
|
d7071d6b4d | ||
|
|
150cd16b1c | ||
|
|
38ac4c53d1 | ||
|
|
9b8bb99039 | ||
|
|
f8bcc81fe6 | ||
|
|
cc18acd69a | ||
|
|
c5a2e5a2e0 | ||
|
|
1532f8f774 | ||
|
|
aa4426c800 | ||
|
|
0140b9beb7 | ||
|
|
8f92993045 | ||
|
|
7697d87358 | ||
|
|
712205d253 | ||
|
|
ede036e94b | ||
|
|
67c16c137b | ||
|
|
c2ced5c925 | ||
|
|
b09ae3f053 | ||
|
|
4c60371ebd | ||
|
|
16be591ed8 | ||
|
|
f75a4f6982 | ||
|
|
330c3e1bf6 | ||
|
|
0ba3cd3bdf | ||
|
|
2cfe11619a | ||
|
|
7607c67070 | ||
|
|
652dfa6ecc | ||
|
|
d394858022 | ||
|
|
26f5ef093f | ||
|
|
714319d67b | ||
|
|
cbaa3180cc | ||
|
|
b4866fd3b1 | ||
|
|
86646bbbdb | ||
|
|
5c4aa664b5 | ||
|
|
184a9df775 | ||
|
|
754d46f1f3 | ||
|
|
683649ff1a | ||
|
|
08ac059a7f | ||
|
|
7c9b0f207c | ||
|
|
f193b8de2c | ||
|
|
812e2132d9 | ||
|
|
282abecd21 | ||
|
|
ba61c91296 | ||
|
|
d49736dd69 | ||
|
|
16b766beef | ||
|
|
8558dcc3a8 | ||
|
|
f8a8b61726 | ||
|
|
067a1de49e | ||
|
|
65ef3bfeca | ||
|
|
af04657856 | ||
|
|
a26c5906d6 | ||
|
|
08a5bff815 | ||
|
|
6089b02746 | ||
|
|
f3f69b1871 | ||
|
|
259f7ef588 | ||
|
|
106a0c9ed8 | ||
|
|
578083dde6 | ||
|
|
9706d7ac64 | ||
|
|
93564c5d52 | ||
|
|
74ba5c0b27 | ||
|
|
bb54a6532d | ||
|
|
3c36c59bb3 | ||
|
|
2308961de6 | ||
|
|
2d71a902f1 | ||
|
|
2e94bfc489 | ||
|
|
1deb903186 | ||
|
|
8c00b91cc6 | ||
|
|
5c8a3f7771 | ||
|
|
70d59be39b | ||
|
|
c562c93158 | ||
|
|
8ac03e311b | ||
|
|
7a430889a8 | ||
|
|
f84b5f163c | ||
|
|
519da49886 | ||
|
|
79d50cb3e0 | ||
|
|
c588c2b2ff | ||
|
|
77a490283c | ||
|
|
e49d26eacd | ||
|
|
7b0fd57eb9 | ||
|
|
7171334e31 | ||
|
|
a3235214b2 | ||
|
|
fca234c45a | ||
|
|
7519023f06 | ||
|
|
ad118095ef | ||
|
|
7ecad94a51 | ||
|
|
328b37322e | ||
|
|
81f7fbc2d5 | ||
|
|
9590ce939a | ||
|
|
fff6fbfbd6 | ||
|
|
52abf8acf3 | ||
|
|
bc61443246 | ||
|
|
cd8594a8b9 | ||
|
|
f3600f64e8 | ||
|
|
752cd57bb1 | ||
|
|
5d6bf131f4 | ||
|
|
8445f45b31 | ||
|
|
df84d7c7b1 | ||
|
|
e837ebec49 | ||
|
|
c7ed693e18 | ||
|
|
e72a25ad02 | ||
|
|
7dab47db16 | ||
|
|
a88ca5a1a8 | ||
|
|
c91d115793 | ||
|
|
e3502bd280 | ||
|
|
8b5ff7c2f9 | ||
|
|
3ac260026b | ||
|
|
80e7fda8ef | ||
|
|
1e7ea399b1 | ||
|
|
9c889a42aa | ||
|
|
952b99599b | ||
|
|
973fa2edc2 | ||
|
|
5e04040f5f | ||
|
|
2c12d5ee29 | ||
|
|
c3f0abadd7 | ||
|
|
adf0a2efca | ||
|
|
e4523c09dc | ||
|
|
91d98b86e1 | ||
|
|
779fb8bce5 | ||
|
|
f0fc83ed85 | ||
|
|
30d2108c78 | ||
|
|
ab68e8a5fe | ||
|
|
2fc9480ae9 | ||
|
|
429afc1e71 | ||
|
|
80da313844 | ||
|
|
31e9100ba2 | ||
|
|
0070f366bb | ||
|
|
2be6865a5c | ||
|
|
db85768e9d | ||
|
|
3d40413882 | ||
|
|
cc88e75950 | ||
|
|
de057dc1b2 | ||
|
|
ff860b04fc | ||
|
|
45dedbbdaa | ||
|
|
b6359ad032 | ||
|
|
a5ae3e5877 | ||
|
|
60ed9d2944 | ||
|
|
91fc4235aa | ||
|
|
42e8dd1361 | ||
|
|
658a02bfc3 | ||
|
|
e1398e8d7c | ||
|
|
0185a1fbd6 | ||
|
|
0a4bbbf173 | ||
|
|
df22f5f7bf | ||
|
|
bb28bb5969 | ||
|
|
3b6c39dc9b | ||
|
|
e4e8e0ff60 | ||
|
|
7e2a35d7a9 | ||
|
|
84e5c00be1 | ||
|
|
187029f44f | ||
|
|
efbc7d1460 | ||
|
|
36f84d083e | ||
|
|
2154ba5451 | ||
|
|
cf0e45c68c | ||
|
|
df5d1e95d1 | ||
|
|
93d9038765 | ||
|
|
f4e8bf9c2e | ||
|
|
9c7f1ae630 | ||
|
|
302eabb30f | ||
|
|
09695f637e | ||
|
|
e9a15fcb83 | ||
|
|
97c8138340 | ||
|
|
a65b0d1f4d | ||
|
|
8d73e2949a | ||
|
|
c0cf647873 | ||
|
|
d20e976176 | ||
|
|
739016ba01 | ||
|
|
a5602eec8d | ||
|
|
867eed176e | ||
|
|
ba883ae104 | ||
|
|
deba7b6220 | ||
|
|
55d6ee3f7e | ||
|
|
69c538858b | ||
|
|
17072dcc45 | ||
|
|
2448f401f2 | ||
|
|
9ef13dad68 | ||
|
|
14fa69719b | ||
|
|
9228b070fa | ||
|
|
5745e3df5f | ||
|
|
d4a5823916 | ||
|
|
929b0196a4 | ||
|
|
1b91f620e1 | ||
|
|
86b15cb580 | ||
|
|
8e5b66a73e | ||
|
|
f755d4c133 | ||
|
|
102c71c4ca | ||
|
|
a7bde80349 | ||
|
|
bedce203ee | ||
|
|
8ba7117fa5 | ||
|
|
fe5d4a0551 | ||
|
|
deebc09a79 | ||
|
|
b63f2fa1fa | ||
|
|
8d9602fb16 | ||
|
|
60b180681e | ||
|
|
7c1c18a329 | ||
|
|
0b0cbb45f4 | ||
|
|
0e03f079a7 | ||
|
|
a71e44b331 | ||
|
|
48917fd8be | ||
|
|
2a054a25ee | ||
|
|
d176c31382 | ||
|
|
8150fef993 | ||
|
|
f0637dcf49 | ||
|
|
0518b90eab | ||
|
|
f968bdd3a9 | ||
|
|
e3a1ea6180 | ||
|
|
17f6054ef0 | ||
|
|
9b8f213c63 | ||
|
|
daccade2e2 | ||
|
|
48bb3e2886 | ||
|
|
308d557770 | ||
|
|
0f4816c674 | ||
|
|
f1b98a530d | ||
|
|
1498eba8d4 | ||
|
|
fc16ffbf2d | ||
|
|
021180fa59 | ||
|
|
102e6a64ad | ||
|
|
79a5c2ec5f | ||
|
|
8cd6e1654f | ||
|
|
63ea21c46d | ||
|
|
0a23dd5311 | ||
|
|
6e3a367832 | ||
|
|
f3348aca4c | ||
|
|
90e1bc9d5e | ||
|
|
453bf3a961 | ||
|
|
b026daec90 | ||
|
|
49f45d27e3 | ||
|
|
479cfb76ef | ||
|
|
0e0cd99ded | ||
|
|
7e210c56b0 | ||
|
|
d92a03048b | ||
|
|
8183699cb7 | ||
|
|
9f9e6c4950 | ||
|
|
c77dd5f200 | ||
|
|
06ac6ae80c | ||
|
|
13e87b1ea0 | ||
|
|
4a32a29bea | ||
|
|
71e165433a | ||
|
|
c2515a4042 | ||
|
|
e31bfdc22b | ||
|
|
e9e4265545 | ||
|
|
9e0777bb42 | ||
|
|
c1532996d8 | ||
|
|
5aa2d9c68d | ||
|
|
6ed5a0f44b | ||
|
|
76845f5f20 | ||
|
|
c931823f62 | ||
|
|
b159182188 | ||
|
|
ca1b8370a0 | ||
|
|
a397da2b93 | ||
|
|
b5acc0e0d6 | ||
|
|
2635c5fcee | ||
|
|
ee2936834a | ||
|
|
c94a5304c7 | ||
|
|
c6b004a474 | ||
|
|
de918ec43b | ||
|
|
069e994b25 | ||
|
|
663692f2d5 | ||
|
|
0ba4761083 | ||
|
|
afad3815a2 | ||
|
|
33f0a11f19 | ||
|
|
6cb3dcd76a | ||
|
|
61e41b539d | ||
|
|
d8cb8869e9 | ||
|
|
57027a1a62 | ||
|
|
92b4a8029d | ||
|
|
7b4dd36827 | ||
|
|
be65042463 | ||
|
|
cccb6a9c02 | ||
|
|
0ac2f53405 | ||
|
|
916c7c49e7 | ||
|
|
3cf5b90f04 | ||
|
|
86efb02358 | ||
|
|
164121d9e4 | ||
|
|
a2d209a34b | ||
|
|
c8adf20804 | ||
|
|
de132c59ea | ||
|
|
e5f6c4ba0f | ||
|
|
0c85835dc2 | ||
|
|
54df8397a7 | ||
|
|
360c17c56e | ||
|
|
c8b98678d0 | ||
|
|
0644032a4f | ||
|
|
2df6b6461b | ||
|
|
44265ac616 | ||
|
|
ac3b953633 | ||
|
|
5de2921d22 | ||
|
|
7363f08a86 | ||
|
|
2322f7e342 | ||
|
|
57f17a08e8 | ||
|
|
eb4e930e63 | ||
|
|
c1a0f8a8d1 | ||
|
|
7e9506391f | ||
|
|
3c7ca56089 | ||
|
|
0d155535c3 | ||
|
|
09a0d2b3b8 | ||
|
|
f0fa2508a9 | ||
|
|
ca1200b689 | ||
|
|
008579363c | ||
|
|
83dcf8d56a | ||
|
|
bfc13bc21b | ||
|
|
786b1ec670 | ||
|
|
573de80a91 | ||
|
|
5afb46f237 | ||
|
|
115340e62d | ||
|
|
b264e539f4 | ||
|
|
c726208d6e | ||
|
|
9cc4fc19d3 | ||
|
|
cc81629f09 | ||
|
|
b1d2fff13f | ||
|
|
8cd706fd95 | ||
|
|
3a4620976e | ||
|
|
e83db7a28a | ||
|
|
7388707a43 | ||
|
|
597f74c84b | ||
|
|
80e193e4ce | ||
|
|
971b124b05 | ||
|
|
83f5c92ff1 | ||
|
|
e39b3bdd35 | ||
|
|
a210ab57b0 | ||
|
|
76fa6ec1b8 | ||
|
|
3f3e0e2ae8 | ||
|
|
65f12ac9ea | ||
|
|
d0941810a7 | ||
|
|
b77deb28b4 | ||
|
|
1ac4466c24 | ||
|
|
03f0061c85 | ||
|
|
c349de6908 | ||
|
|
fd7f3a646e | ||
|
|
7244c1bebc | ||
|
|
20df5eeb8f | ||
|
|
23f7dd94b6 | ||
|
|
7125da4533 | ||
|
|
684cb59a7c | ||
|
|
9274fe9a10 | ||
|
|
a21f083761 | ||
|
|
c7e2834fc6 | ||
|
|
a08c26b076 | ||
|
|
f4aa88e1ff | ||
|
|
53eab7aa29 | ||
|
|
8374d61f52 | ||
|
|
4c943b7575 | ||
|
|
24032b57f6 | ||
|
|
8628c774e5 | ||
|
|
523f044914 | ||
|
|
892c9ad040 | ||
|
|
570f39c620 | ||
|
|
a73316ef9f | ||
|
|
6d6195ae6a | ||
|
|
4ba66c7018 | ||
|
|
54b9424c6e | ||
|
|
af574634b0 | ||
|
|
d1e1c09b4a | ||
|
|
4f5a720c30 | ||
|
|
4ddfdb84ac |
@@ -86,5 +86,6 @@
|
|||||||
"RATE_LIMITER_ENABLED": "false",
|
"RATE_LIMITER_ENABLED": "false",
|
||||||
"REDIS_HOST": "aaabbbcccdddeeefff",
|
"REDIS_HOST": "aaabbbcccdddeeefff",
|
||||||
"REDIS_PORT": "1234",
|
"REDIS_PORT": "1234",
|
||||||
"REDIS_PASSWORD": "12345678"
|
"REDIS_PASSWORD": "12345678",
|
||||||
|
"TRUSTED_DOMAINS": "localhost,habitica.com"
|
||||||
}
|
}
|
||||||
|
|||||||
Submodule habitica-images updated: 428d395f36...109539e445
158
migrations/archive/2023/20230522_pet_group_achievements.js
Normal file
158
migrations/archive/2023/20230522_pet_group_achievements.js
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
/* eslint-disable no-console */
|
||||||
|
const MIGRATION_NAME = '20230522_pet_group_achievements';
|
||||||
|
import { model as User } from '../../../website/server/models/user';
|
||||||
|
|
||||||
|
const progressCount = 1000;
|
||||||
|
let count = 0;
|
||||||
|
|
||||||
|
async function updateUser (user) {
|
||||||
|
count++;
|
||||||
|
|
||||||
|
const set = {
|
||||||
|
migration: MIGRATION_NAME,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (user && user.items && user.items.pets) {
|
||||||
|
const pets = user.items.pets;
|
||||||
|
if (pets['Parrot-Base']
|
||||||
|
&& pets['Parrot-CottonCandyBlue']
|
||||||
|
&& pets['Parrot-CottonCandyPink']
|
||||||
|
&& pets['Parrot-Desert']
|
||||||
|
&& pets['Parrot-Golden']
|
||||||
|
&& pets['Parrot-Red']
|
||||||
|
&& pets['Parrot-Shade']
|
||||||
|
&& pets['Parrot-Skeleton']
|
||||||
|
&& pets['Parrot-White']
|
||||||
|
&& pets['Parrot-Zombie']
|
||||||
|
&& pets['Rooster-Base']
|
||||||
|
&& pets['Rooster-CottonCandyBlue']
|
||||||
|
&& pets['Rooster-CottonCandyPink']
|
||||||
|
&& pets['Rooster-Desert']
|
||||||
|
&& pets['Rooster-Golden']
|
||||||
|
&& pets['Rooster-Red']
|
||||||
|
&& pets['Rooster-Shade']
|
||||||
|
&& pets['Rooster-Skeleton']
|
||||||
|
&& pets['Rooster-White']
|
||||||
|
&& pets['Rooster-Zombie']
|
||||||
|
&& pets['Triceratops-Base']
|
||||||
|
&& pets['Triceratops-CottonCandyBlue']
|
||||||
|
&& pets['Triceratops-CottonCandyPink']
|
||||||
|
&& pets['Triceratops-Desert']
|
||||||
|
&& pets['Triceratops-Golden']
|
||||||
|
&& pets['Triceratops-Red']
|
||||||
|
&& pets['Triceratops-Shade']
|
||||||
|
&& pets['Triceratops-Skeleton']
|
||||||
|
&& pets['Triceratops-White']
|
||||||
|
&& pets['Triceratops-Zombie']
|
||||||
|
&& pets['TRex-Base']
|
||||||
|
&& pets['TRex-CottonCandyBlue']
|
||||||
|
&& pets['TRex-CottonCandyPink']
|
||||||
|
&& pets['TRex-Desert']
|
||||||
|
&& pets['TRex-Golden']
|
||||||
|
&& pets['TRex-Red']
|
||||||
|
&& pets['TRex-Shade']
|
||||||
|
&& pets['TRex-Skeleton']
|
||||||
|
&& pets['TRex-White']
|
||||||
|
&& pets['TRex-Zombie']
|
||||||
|
&& pets['Pterodactyl-Base']
|
||||||
|
&& pets['Pterodactyl-CottonCandyBlue']
|
||||||
|
&& pets['Pterodactyl-CottonCandyPink']
|
||||||
|
&& pets['Pterodactyl-Desert']
|
||||||
|
&& pets['Pterodactyl-Golden']
|
||||||
|
&& pets['Pterodactyl-Red']
|
||||||
|
&& pets['Pterodactyl-Shade']
|
||||||
|
&& pets['Pterodactyl-Skeleton']
|
||||||
|
&& pets['Pterodactyl-White']
|
||||||
|
&& pets['Pterodactyl-Zombie']
|
||||||
|
&& pets['Owl-Base']
|
||||||
|
&& pets['Owl-CottonCandyBlue']
|
||||||
|
&& pets['Owl-CottonCandyPink']
|
||||||
|
&& pets['Owl-Desert']
|
||||||
|
&& pets['Owl-Golden']
|
||||||
|
&& pets['Owl-Red']
|
||||||
|
&& pets['Owl-Shade']
|
||||||
|
&& pets['Owl-Skeleton']
|
||||||
|
&& pets['Owl-White']
|
||||||
|
&& pets['Owl-Zombie']
|
||||||
|
&& pets['Velociraptor-Base']
|
||||||
|
&& pets['Velociraptor-CottonCandyBlue']
|
||||||
|
&& pets['Velociraptor-CottonCandyPink']
|
||||||
|
&& pets['Velociraptor-Desert']
|
||||||
|
&& pets['Velociraptor-Golden']
|
||||||
|
&& pets['Velociraptor-Red']
|
||||||
|
&& pets['Velociraptor-Shade']
|
||||||
|
&& pets['Velociraptor-Skeleton']
|
||||||
|
&& pets['Velociraptor-White']
|
||||||
|
&& pets['Velociraptor-Zombie']
|
||||||
|
&& pets['Penguin-Base']
|
||||||
|
&& pets['Penguin-CottonCandyBlue']
|
||||||
|
&& pets['Penguin-CottonCandyPink']
|
||||||
|
&& pets['Penguin-Desert']
|
||||||
|
&& pets['Penguin-Golden']
|
||||||
|
&& pets['Penguin-Red']
|
||||||
|
&& pets['Penguin-Shade']
|
||||||
|
&& pets['Penguin-Skeleton']
|
||||||
|
&& pets['Penguin-White']
|
||||||
|
&& pets['Penguin-Zombie']
|
||||||
|
&& pets['Falcon-Base']
|
||||||
|
&& pets['Falcon-CottonCandyBlue']
|
||||||
|
&& pets['Falcon-CottonCandyPink']
|
||||||
|
&& pets['Falcon-Desert']
|
||||||
|
&& pets['Falcon-Golden']
|
||||||
|
&& pets['Falcon-Red']
|
||||||
|
&& pets['Falcon-Shade']
|
||||||
|
&& pets['Falcon-Skeleton']
|
||||||
|
&& pets['Falcon-White']
|
||||||
|
&& pets['Falcon-Zombie']
|
||||||
|
&& pets['Peacock-Base']
|
||||||
|
&& pets['Peacock-CottonCandyBlue']
|
||||||
|
&& pets['Peacock-CottonCandyPink']
|
||||||
|
&& pets['Peacock-Desert']
|
||||||
|
&& pets['Peacock-Golden']
|
||||||
|
&& pets['Peacock-Red']
|
||||||
|
&& pets['Peacock-Shade']
|
||||||
|
&& pets['Peacock-Skeleton']
|
||||||
|
&& pets['Peacock-White']
|
||||||
|
&& pets['Peacock-Zombie']) {
|
||||||
|
set['achievements.dinosaurDynasty'] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
|
||||||
|
|
||||||
|
return await User.update({ _id: user._id }, { $set: set }).exec();
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function processUsers () {
|
||||||
|
let query = {
|
||||||
|
// migration: { $ne: MIGRATION_NAME },
|
||||||
|
'auth.timestamps.loggedin': { $gt: new Date('2023-04-15') },
|
||||||
|
};
|
||||||
|
|
||||||
|
const fields = {
|
||||||
|
_id: 1,
|
||||||
|
items: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
while (true) { // eslint-disable-line no-constant-condition
|
||||||
|
const users = await User // eslint-disable-line no-await-in-loop
|
||||||
|
.find(query)
|
||||||
|
.limit(250)
|
||||||
|
.sort({_id: 1})
|
||||||
|
.select(fields)
|
||||||
|
.lean()
|
||||||
|
.exec();
|
||||||
|
|
||||||
|
if (users.length === 0) {
|
||||||
|
console.warn('All appropriate users found and modified.');
|
||||||
|
console.warn(`\n${count} users processed\n`);
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
query._id = {
|
||||||
|
$gt: users[users.length - 1]._id,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
|
||||||
|
}
|
||||||
|
};
|
||||||
79
migrations/archive/2023/20230718_summer_splash_orcas.js
Normal file
79
migrations/archive/2023/20230718_summer_splash_orcas.js
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
/* eslint-disable no-console */
|
||||||
|
const MIGRATION_NAME = '20230718_summer_splash_orcas';
|
||||||
|
|
||||||
|
import { model as User } from '../../../website/server/models/user';
|
||||||
|
|
||||||
|
const progressCount = 1000;
|
||||||
|
let count = 0;
|
||||||
|
|
||||||
|
async function updateUser (user) {
|
||||||
|
count++;
|
||||||
|
|
||||||
|
const set = { migration: MIGRATION_NAME };
|
||||||
|
const push = {};
|
||||||
|
|
||||||
|
if (user && user.items && user.items.pets && typeof user.items.pets['Orca-Base'] !== 'undefined') {
|
||||||
|
return;
|
||||||
|
} else if (user && user.items && user.items.mounts && typeof user.items.mounts['Orca-Base'] !== 'undefined') {
|
||||||
|
set['items.pets.Orca-Base'] = 5;
|
||||||
|
push.notifications = {
|
||||||
|
type: 'ITEM_RECEIVED',
|
||||||
|
data: {
|
||||||
|
icon: 'notif_orca_pet',
|
||||||
|
title: 'Orcas for Summer Splash!',
|
||||||
|
text: 'To celebrate Summer Splash, we\'ve given you an Orca Pet!',
|
||||||
|
destination: 'stable',
|
||||||
|
},
|
||||||
|
seen: false,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
set['items.mounts.Orca-Base'] = true;
|
||||||
|
push.notifications = {
|
||||||
|
type: 'ITEM_RECEIVED',
|
||||||
|
data: {
|
||||||
|
icon: 'notif_orca_mount',
|
||||||
|
title: 'Orcas for Summer Splash!',
|
||||||
|
text: 'To celebrate Summer Splash, we\'ve given you an Orca Mount!',
|
||||||
|
destination: 'stable',
|
||||||
|
},
|
||||||
|
seen: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
|
||||||
|
|
||||||
|
return await user.updateOne({ $set: set, $push: push }).exec();
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function processUsers () {
|
||||||
|
let query = {
|
||||||
|
migration: {$ne: MIGRATION_NAME},
|
||||||
|
'auth.timestamps.loggedin': {$gt: new Date('2023-06-18')},
|
||||||
|
};
|
||||||
|
|
||||||
|
const fields = {
|
||||||
|
_id: 1,
|
||||||
|
items: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
while (true) { // eslint-disable-line no-constant-condition
|
||||||
|
const users = await User // eslint-disable-line no-await-in-loop
|
||||||
|
.find(query)
|
||||||
|
.limit(250)
|
||||||
|
.sort({_id: 1})
|
||||||
|
.select(fields)
|
||||||
|
.exec();
|
||||||
|
|
||||||
|
if (users.length === 0) {
|
||||||
|
console.warn('All appropriate users found and modified.');
|
||||||
|
console.warn(`\n${count} users processed\n`);
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
query._id = {
|
||||||
|
$gt: users[users.length - 1],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
|
||||||
|
}
|
||||||
|
};
|
||||||
155
migrations/archive/2023/20230731_naming_day.js
Normal file
155
migrations/archive/2023/20230731_naming_day.js
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
/* eslint-disable no-console */
|
||||||
|
const MIGRATION_NAME = '20230731_naming_day';
|
||||||
|
import { v4 as uuid } from 'uuid';
|
||||||
|
|
||||||
|
import { model as User } from '../../../website/server/models/user';
|
||||||
|
|
||||||
|
const progressCount = 1000;
|
||||||
|
let count = 0;
|
||||||
|
|
||||||
|
async function updateUser (user) {
|
||||||
|
count++;
|
||||||
|
|
||||||
|
let set;
|
||||||
|
let push;
|
||||||
|
const inc = {
|
||||||
|
'items.food.Cake_Base': 1,
|
||||||
|
'items.food.Cake_CottonCandyBlue': 1,
|
||||||
|
'items.food.Cake_CottonCandyPink': 1,
|
||||||
|
'items.food.Cake_Desert': 1,
|
||||||
|
'items.food.Cake_Golden': 1,
|
||||||
|
'items.food.Cake_Red': 1,
|
||||||
|
'items.food.Cake_Shade': 1,
|
||||||
|
'items.food.Cake_Skeleton': 1,
|
||||||
|
'items.food.Cake_White': 1,
|
||||||
|
'items.food.Cake_Zombie': 1,
|
||||||
|
'achievements.habiticaDays': 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (user && user.items && user.items.gear && user.items.gear.owned && typeof user.items.gear.owned.back_special_namingDay2020 !== 'undefined') {
|
||||||
|
set = { migration: MIGRATION_NAME };
|
||||||
|
push = {
|
||||||
|
notifications: {
|
||||||
|
type: 'ITEM_RECEIVED',
|
||||||
|
data: {
|
||||||
|
icon: 'notif_namingDay_cake',
|
||||||
|
title: 'Happy Naming Day!',
|
||||||
|
text: 'To celebrate the day we became Habitica, we’ve awarded you some cake!',
|
||||||
|
destination: '/inventory/items',
|
||||||
|
},
|
||||||
|
seen: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} else if (user && user.items && user.items.gear && user.items.gear.owned && typeof user.items.gear.owned.body_special_namingDay2018 !== 'undefined') {
|
||||||
|
set = { migration: MIGRATION_NAME, 'items.gear.owned.back_special_namingDay2020': true };
|
||||||
|
push = {
|
||||||
|
notifications: {
|
||||||
|
type: 'ITEM_RECEIVED',
|
||||||
|
data: {
|
||||||
|
icon: 'notif_namingDay_back',
|
||||||
|
title: 'Happy Naming Day!',
|
||||||
|
text: 'To celebrate the day we became Habitica, we’ve awarded you a Royal Purple Gryphon Tail and cake!',
|
||||||
|
destination: '/inventory/equipment',
|
||||||
|
},
|
||||||
|
seen: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} else if (user && user.items && user.items.gear && user.items.gear.owned && typeof user.items.gear.owned.head_special_namingDay2017 !== 'undefined') {
|
||||||
|
set = { migration: MIGRATION_NAME, 'items.gear.owned.body_special_namingDay2018': true };
|
||||||
|
push = {
|
||||||
|
notifications: {
|
||||||
|
type: 'ITEM_RECEIVED',
|
||||||
|
data: {
|
||||||
|
icon: 'notif_namingDay_body',
|
||||||
|
title: 'Happy Naming Day!',
|
||||||
|
text: 'To celebrate the day we became Habitica, we’ve awarded you a Royal Purple Gryphon Cloak and cake!',
|
||||||
|
destination: '/inventory/equipment',
|
||||||
|
},
|
||||||
|
seen: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} else if (user && user.items && user.items.pets && typeof user.items.pets['Gryphon-RoyalPurple'] !== 'undefined') {
|
||||||
|
set = { migration: MIGRATION_NAME, 'items.gear.owned.head_special_namingDay2017': true };
|
||||||
|
push = {
|
||||||
|
notifications: {
|
||||||
|
type: 'ITEM_RECEIVED',
|
||||||
|
data: {
|
||||||
|
icon: 'notif_namingDay_head',
|
||||||
|
title: 'Happy Naming Day!',
|
||||||
|
text: 'To celebrate the day we became Habitica, we’ve awarded you a Royal Purple Gryphon Helm and cake!',
|
||||||
|
destination: '/inventory/equipment',
|
||||||
|
},
|
||||||
|
seen: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} else if (user && user.items && user.items.mounts && typeof user.items.mounts['Gryphon-RoyalPurple'] !== 'undefined') {
|
||||||
|
set = { migration: MIGRATION_NAME, 'items.pets.Gryphon-RoyalPurple': 5 };
|
||||||
|
push = {
|
||||||
|
notifications: {
|
||||||
|
type: 'ITEM_RECEIVED',
|
||||||
|
data: {
|
||||||
|
icon: 'notif_namingDay_pet',
|
||||||
|
title: 'Happy Naming Day!',
|
||||||
|
text: 'To celebrate the day we became Habitica, we’ve awarded you a Royal Purple Gryphon Pet and cake!',
|
||||||
|
destination: '/inventory/stable',
|
||||||
|
},
|
||||||
|
seen: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
set = { migration: MIGRATION_NAME, 'items.mounts.Gryphon-RoyalPurple': true };
|
||||||
|
push = {
|
||||||
|
notifications: {
|
||||||
|
type: 'ITEM_RECEIVED',
|
||||||
|
data: {
|
||||||
|
icon: 'notif_namingDay_mount',
|
||||||
|
title: 'Happy Naming Day!',
|
||||||
|
text: 'To celebrate the day we became Habitica, we’ve awarded you a Royal Purple Gryphon Mount and cake!',
|
||||||
|
destination: '/inventory/stable',
|
||||||
|
},
|
||||||
|
seen: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
|
||||||
|
|
||||||
|
if (push) {
|
||||||
|
return await user.updateOne({ $set: set, $inc: inc, $push: push }).exec();
|
||||||
|
} else {
|
||||||
|
return await user.updateOne({ $set: set, $inc: inc }).exec();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function processUsers () {
|
||||||
|
let query = {
|
||||||
|
migration: { $ne: MIGRATION_NAME },
|
||||||
|
'auth.timestamps.loggedin': { $gt: new Date('2023-07-01') },
|
||||||
|
};
|
||||||
|
|
||||||
|
const fields = {
|
||||||
|
_id: 1,
|
||||||
|
items: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
while (true) { // eslint-disable-line no-constant-condition
|
||||||
|
const users = await User // eslint-disable-line no-await-in-loop
|
||||||
|
.find(query)
|
||||||
|
.limit(250)
|
||||||
|
.sort({_id: 1})
|
||||||
|
.select(fields)
|
||||||
|
.exec();
|
||||||
|
|
||||||
|
if (users.length === 0) {
|
||||||
|
console.warn('All appropriate users found and modified.');
|
||||||
|
console.warn(`\n${count} users processed\n`);
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
query._id = {
|
||||||
|
$gt: users[users.length - 1]._id,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
|
||||||
|
}
|
||||||
|
};
|
||||||
72
migrations/archive/2023/20230808_guild_gems.js
Normal file
72
migrations/archive/2023/20230808_guild_gems.js
Normal file
@@ -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
|
||||||
|
}
|
||||||
|
};
|
||||||
62
migrations/archive/2023/20230808_guild_gems_fix.js
Normal file
62
migrations/archive/2023/20230808_guild_gems_fix.js
Normal file
@@ -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
|
||||||
|
}
|
||||||
|
};
|
||||||
144
migrations/archive/2023/20230808_veteran_pet_ladder.js
Normal file
144
migrations/archive/2023/20230808_veteran_pet_ladder.js
Normal file
@@ -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
|
||||||
|
}
|
||||||
|
};
|
||||||
3071
package-lock.json
generated
3071
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
26
package.json
26
package.json
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "habitica",
|
"name": "habitica",
|
||||||
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
|
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
|
||||||
"version": "4.266.0",
|
"version": "5.1.2",
|
||||||
"main": "./website/server/index.js",
|
"main": "./website/server/index.js",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/core": "^7.20.12",
|
"@babel/core": "^7.22.5",
|
||||||
"@babel/preset-env": "^7.20.2",
|
"@babel/preset-env": "^7.22.5",
|
||||||
"@babel/register": "^7.18.9",
|
"@babel/register": "^7.22.5",
|
||||||
"@google-cloud/trace-agent": "^7.1.2",
|
"@google-cloud/trace-agent": "^7.1.2",
|
||||||
"@parse/node-apn": "^5.1.3",
|
"@parse/node-apn": "^5.1.3",
|
||||||
"@slack/webhook": "^6.1.0",
|
"@slack/webhook": "^6.1.0",
|
||||||
@@ -14,9 +14,9 @@
|
|||||||
"amazon-payments": "^0.2.9",
|
"amazon-payments": "^0.2.9",
|
||||||
"amplitude": "^6.0.0",
|
"amplitude": "^6.0.0",
|
||||||
"apidoc": "^0.54.0",
|
"apidoc": "^0.54.0",
|
||||||
"apple-auth": "^1.0.7",
|
"apple-auth": "^1.0.9",
|
||||||
"bcrypt": "^5.1.0",
|
"bcrypt": "^5.1.0",
|
||||||
"body-parser": "^1.20.1",
|
"body-parser": "^1.20.2",
|
||||||
"bootstrap": "^4.6.0",
|
"bootstrap": "^4.6.0",
|
||||||
"compression": "^1.7.4",
|
"compression": "^1.7.4",
|
||||||
"cookie-session": "^2.0.0",
|
"cookie-session": "^2.0.0",
|
||||||
@@ -42,7 +42,7 @@
|
|||||||
"image-size": "^1.0.2",
|
"image-size": "^1.0.2",
|
||||||
"in-app-purchase": "^1.11.3",
|
"in-app-purchase": "^1.11.3",
|
||||||
"js2xmlparser": "^5.0.0",
|
"js2xmlparser": "^5.0.0",
|
||||||
"jsonwebtoken": "^8.5.1",
|
"jsonwebtoken": "^9.0.0",
|
||||||
"jwks-rsa": "^2.1.5",
|
"jwks-rsa": "^2.1.5",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"merge-stream": "^2.0.0",
|
"merge-stream": "^2.0.0",
|
||||||
@@ -67,16 +67,16 @@
|
|||||||
"remove-markdown": "^0.5.0",
|
"remove-markdown": "^0.5.0",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"short-uuid": "^4.2.2",
|
"short-uuid": "^4.2.2",
|
||||||
"stripe": "^11.10.0",
|
"stripe": "^12.9.0",
|
||||||
"superagent": "^8.0.6",
|
"superagent": "^8.0.9",
|
||||||
"universal-analytics": "^0.5.3",
|
"universal-analytics": "^0.5.3",
|
||||||
"useragent": "^2.1.9",
|
"useragent": "^2.1.9",
|
||||||
"uuid": "^9.0.0",
|
"uuid": "^9.0.0",
|
||||||
"validator": "^13.9.0",
|
"validator": "^13.9.0",
|
||||||
"vinyl-buffer": "^1.0.1",
|
"vinyl-buffer": "^1.0.1",
|
||||||
"winston": "^3.8.2",
|
"winston": "^3.9.0",
|
||||||
"winston-loggly-bulk": "^3.2.1",
|
"winston-loggly-bulk": "^3.2.1",
|
||||||
"xml2js": "^0.4.23"
|
"xml2js": "^0.6.0"
|
||||||
},
|
},
|
||||||
"private": true,
|
"private": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -110,7 +110,7 @@
|
|||||||
"apidoc": "gulp apidoc"
|
"apidoc": "gulp apidoc"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"axios": "^1.2.2",
|
"axios": "^1.3.6",
|
||||||
"chai": "^4.3.7",
|
"chai": "^4.3.7",
|
||||||
"chai-as-promised": "^7.1.1",
|
"chai-as-promised": "^7.1.1",
|
||||||
"chai-moment": "^0.1.0",
|
"chai-moment": "^0.1.0",
|
||||||
@@ -122,7 +122,7 @@
|
|||||||
"monk": "^7.3.4",
|
"monk": "^7.3.4",
|
||||||
"require-again": "^2.0.0",
|
"require-again": "^2.0.0",
|
||||||
"run-rs": "^0.7.7",
|
"run-rs": "^0.7.7",
|
||||||
"sinon": "^15.0.1",
|
"sinon": "^15.1.2",
|
||||||
"sinon-chai": "^3.7.0",
|
"sinon-chai": "^3.7.0",
|
||||||
"sinon-stub-promise": "^4.0.0"
|
"sinon-stub-promise": "^4.0.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -748,9 +748,19 @@ describe('payments/index', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('does not add to plans.consecutive.offset if 1 month subscription', async () => {
|
it('does not add to plans.consecutive.offset if 1 month subscription', async () => {
|
||||||
|
data.sub.key = 'basic_earned';
|
||||||
await api.createSubscription(data);
|
await api.createSubscription(data);
|
||||||
|
|
||||||
expect(user.purchased.plan.extraMonths).to.eql(0);
|
expect(user.purchased.plan.consecutive.offset).to.eql(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('resets plans.consecutive.offset if 1 month subscription', async () => {
|
||||||
|
user.purchased.plan.consecutive.offset = 1;
|
||||||
|
await user.save();
|
||||||
|
data.sub.key = 'basic_earned';
|
||||||
|
await api.createSubscription(data);
|
||||||
|
|
||||||
|
expect(user.purchased.plan.consecutive.offset).to.eql(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('adds 5 to plan.consecutive.gemCapExtra for 3 month block', async () => {
|
it('adds 5 to plan.consecutive.gemCapExtra for 3 month block', async () => {
|
||||||
|
|||||||
@@ -242,7 +242,7 @@ describe('cron middleware', () => {
|
|||||||
|
|
||||||
sandbox.spy(cronLib, 'recoverCron');
|
sandbox.spy(cronLib, 'recoverCron');
|
||||||
|
|
||||||
sandbox.stub(User, 'update')
|
sandbox.stub(User, 'updateOne')
|
||||||
.withArgs({
|
.withArgs({
|
||||||
_id: user._id,
|
_id: user._id,
|
||||||
$or: [
|
$or: [
|
||||||
|
|||||||
@@ -1732,7 +1732,7 @@ describe('Group Model', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('updates participting members (not including user)', async () => {
|
it('updates participting members (not including user)', async () => {
|
||||||
sandbox.spy(User, 'update');
|
sandbox.spy(User, 'updateMany');
|
||||||
|
|
||||||
await party.startQuest(nonParticipatingMember);
|
await party.startQuest(nonParticipatingMember);
|
||||||
|
|
||||||
@@ -1740,7 +1740,7 @@ describe('Group Model', () => {
|
|||||||
questLeader._id, participatingMember._id, sleepingParticipatingMember._id,
|
questLeader._id, participatingMember._id, sleepingParticipatingMember._id,
|
||||||
];
|
];
|
||||||
|
|
||||||
expect(User.update).to.be.calledWith(
|
expect(User.updateMany).to.be.calledWith(
|
||||||
{ _id: { $in: members } },
|
{ _id: { $in: members } },
|
||||||
{
|
{
|
||||||
$set: {
|
$set: {
|
||||||
@@ -1753,11 +1753,11 @@ describe('Group Model', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('updates non-user quest leader and decrements quest scroll', async () => {
|
it('updates non-user quest leader and decrements quest scroll', async () => {
|
||||||
sandbox.spy(User, 'update');
|
sandbox.spy(User, 'updateOne');
|
||||||
|
|
||||||
await party.startQuest(participatingMember);
|
await party.startQuest(participatingMember);
|
||||||
|
|
||||||
expect(User.update).to.be.calledWith(
|
expect(User.updateOne).to.be.calledWith(
|
||||||
{ _id: questLeader._id },
|
{ _id: questLeader._id },
|
||||||
{
|
{
|
||||||
$inc: {
|
$inc: {
|
||||||
@@ -1819,29 +1819,29 @@ describe('Group Model', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
it('doesn\'t retry successful operations', async () => {
|
it('doesn\'t retry successful operations', async () => {
|
||||||
sandbox.stub(User, 'update').returns(successfulMock);
|
sandbox.stub(User, 'updateOne').returns(successfulMock);
|
||||||
|
|
||||||
await party.finishQuest(quest);
|
await party.finishQuest(quest);
|
||||||
|
|
||||||
expect(User.update).to.be.calledThrice;
|
expect(User.updateOne).to.be.calledThrice;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('stops retrying when a successful update has occurred', async () => {
|
it('stops retrying when a successful update has occurred', async () => {
|
||||||
const updateStub = sandbox.stub(User, 'update');
|
const updateStub = sandbox.stub(User, 'updateOne');
|
||||||
updateStub.onCall(0).returns(failedMock);
|
updateStub.onCall(0).returns(failedMock);
|
||||||
updateStub.returns(successfulMock);
|
updateStub.returns(successfulMock);
|
||||||
|
|
||||||
await party.finishQuest(quest);
|
await party.finishQuest(quest);
|
||||||
|
|
||||||
expect(User.update.callCount).to.equal(4);
|
expect(User.updateOne.callCount).to.equal(4);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('retries failed updates at most five times per user', async () => {
|
it('retries failed updates at most five times per user', async () => {
|
||||||
sandbox.stub(User, 'update').returns(failedMock);
|
sandbox.stub(User, 'updateOne').returns(failedMock);
|
||||||
|
|
||||||
await expect(party.finishQuest(quest)).to.eventually.be.rejected;
|
await expect(party.finishQuest(quest)).to.eventually.be.rejected;
|
||||||
|
|
||||||
expect(User.update.callCount).to.eql(15); // for 3 users
|
expect(User.updateOne.callCount).to.eql(15); // for 3 users
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -2088,17 +2088,17 @@ describe('Group Model', () => {
|
|||||||
|
|
||||||
context('Party quests', () => {
|
context('Party quests', () => {
|
||||||
it('updates participating members with rewards', async () => {
|
it('updates participating members with rewards', async () => {
|
||||||
sandbox.spy(User, 'update');
|
sandbox.spy(User, 'updateOne');
|
||||||
await party.finishQuest(quest);
|
await party.finishQuest(quest);
|
||||||
|
|
||||||
expect(User.update).to.be.calledThrice;
|
expect(User.updateOne).to.be.calledThrice;
|
||||||
expect(User.update).to.be.calledWithMatch({
|
expect(User.updateOne).to.be.calledWithMatch({
|
||||||
_id: questLeader._id,
|
_id: questLeader._id,
|
||||||
});
|
});
|
||||||
expect(User.update).to.be.calledWithMatch({
|
expect(User.updateOne).to.be.calledWithMatch({
|
||||||
_id: participatingMember._id,
|
_id: participatingMember._id,
|
||||||
});
|
});
|
||||||
expect(User.update).to.be.calledWithMatch({
|
expect(User.updateOne).to.be.calledWithMatch({
|
||||||
_id: sleepingParticipatingMember._id,
|
_id: sleepingParticipatingMember._id,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -2173,11 +2173,11 @@ describe('Group Model', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('updates all users with rewards', async () => {
|
it('updates all users with rewards', async () => {
|
||||||
sandbox.spy(User, 'update');
|
sandbox.spy(User, 'updateMany');
|
||||||
await party.finishQuest(tavernQuest);
|
await party.finishQuest(tavernQuest);
|
||||||
|
|
||||||
expect(User.update).to.be.calledOnce;
|
expect(User.updateMany).to.be.calledOnce;
|
||||||
expect(User.update).to.be.calledWithMatch({});
|
expect(User.updateMany).to.be.calledWithMatch({});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('sets quest completed to the world quest key', async () => {
|
it('sets quest completed to the world quest key', async () => {
|
||||||
|
|||||||
@@ -16,60 +16,7 @@ describe('GET /challenges/:challengeId', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
context('public guild', () => {
|
context('Group Plan', () => {
|
||||||
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', () => {
|
|
||||||
let groupLeader;
|
let groupLeader;
|
||||||
let challengeLeader;
|
let challengeLeader;
|
||||||
let group;
|
let group;
|
||||||
@@ -84,14 +31,14 @@ describe('GET /challenges/:challengeId', () => {
|
|||||||
const populatedGroup = await createAndPopulateGroup({
|
const populatedGroup = await createAndPopulateGroup({
|
||||||
groupDetails: { type: 'guild', privacy: 'private' },
|
groupDetails: { type: 'guild', privacy: 'private' },
|
||||||
members: 2,
|
members: 2,
|
||||||
|
upgradeToGroupPlan: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
groupLeader = populatedGroup.groupLeader;
|
groupLeader = populatedGroup.groupLeader;
|
||||||
group = populatedGroup.group;
|
group = populatedGroup.group;
|
||||||
members = populatedGroup.members;
|
members = populatedGroup.members;
|
||||||
|
|
||||||
challengeLeader = members[0]; // eslint-disable-line prefer-destructuring
|
[challengeLeader, otherMember] = members;
|
||||||
otherMember = members[1]; // eslint-disable-line prefer-destructuring
|
|
||||||
|
|
||||||
challenge = await generateChallenge(challengeLeader, group);
|
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 () => {
|
it('populates only some fields', async () => {
|
||||||
const anotherUser = await generateUser({ balance: 3 });
|
const group = await generateGroup(user, { type: 'party', privacy: 'private', name: generateUUID() });
|
||||||
const group = await generateGroup(anotherUser, { type: 'guild', privacy: 'public', name: generateUUID() });
|
const challenge = await generateChallenge(user, group);
|
||||||
const challenge = await generateChallenge(anotherUser, group);
|
await user.post(`/challenges/${challenge._id}/join`);
|
||||||
await anotherUser.post(`/challenges/${challenge._id}/join`);
|
|
||||||
const res = await user.get(`/challenges/${challenge._id}/members`);
|
const res = await user.get(`/challenges/${challenge._id}/members`);
|
||||||
expect(res[0]).to.eql({
|
expect(res[0]).to.eql({
|
||||||
_id: anotherUser._id,
|
_id: user._id,
|
||||||
id: anotherUser._id,
|
id: user._id,
|
||||||
profile: { name: anotherUser.profile.name },
|
profile: { name: user.profile.name },
|
||||||
auth: {
|
auth: {
|
||||||
local: {
|
local: {
|
||||||
username: anotherUser.auth.local.username,
|
username: user.auth.local.username,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
flags: {
|
flags: {
|
||||||
|
|||||||
@@ -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 () => {
|
it('returns the member tasks for the challenges', async () => {
|
||||||
const group = await generateGroup(user, { type: 'party', name: generateUUID() });
|
const group = await generateGroup(user, { type: 'party', name: generateUUID() });
|
||||||
const challenge = await generateChallenge(user, group);
|
const challenge = await generateChallenge(user, group);
|
||||||
|
|||||||
@@ -7,117 +7,7 @@ import {
|
|||||||
import { TAVERN_ID } from '../../../../../website/common/script/constants';
|
import { TAVERN_ID } from '../../../../../website/common/script/constants';
|
||||||
|
|
||||||
describe('GET challenges/groups/:groupId', () => {
|
describe('GET challenges/groups/:groupId', () => {
|
||||||
context('Public Guild', () => {
|
context('Group Plan', () => {
|
||||||
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', () => {
|
|
||||||
let privateGuild; let user; let nonMember; let challenge; let
|
let privateGuild; let user; let nonMember; let challenge; let
|
||||||
challenge2;
|
challenge2;
|
||||||
|
|
||||||
@@ -128,6 +18,7 @@ describe('GET challenges/groups/:groupId', () => {
|
|||||||
type: 'guild',
|
type: 'guild',
|
||||||
privacy: 'private',
|
privacy: 'private',
|
||||||
},
|
},
|
||||||
|
upgradeToGroupPlan: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
privateGuild = group;
|
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', () => {
|
context('Party', () => {
|
||||||
let party; let user; let nonMember; let challenge; let
|
let party; let user; let nonMember; let challenge; let
|
||||||
challenge2;
|
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 challenges = await user.get('/challenges/groups/habitrpg');
|
||||||
|
|
||||||
const foundChallenge1 = _.find(challenges, { _id: challenge._id });
|
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,
|
generateUser,
|
||||||
generateChallenge,
|
generateChallenge,
|
||||||
createAndPopulateGroup,
|
createAndPopulateGroup,
|
||||||
|
resetHabiticaDB,
|
||||||
} from '../../../../helpers/api-integration/v3';
|
} from '../../../../helpers/api-integration/v3';
|
||||||
|
import { TAVERN_ID } from '../../../../../website/common/script/constants';
|
||||||
|
|
||||||
describe('GET challenges/user', () => {
|
describe('GET challenges/user', () => {
|
||||||
context('no official challenges', () => {
|
context('no official challenges', () => {
|
||||||
let user; let member; let nonMember; let challenge; let challenge2;
|
let user; let member; let nonMember; let challenge; let challenge2; let publicChallenge;
|
||||||
let publicGuild; let userData; let groupData;
|
let groupPlan; let userData; let groupData; let tavern; let tavernData;
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
|
await resetHabiticaDB();
|
||||||
|
|
||||||
const { group, groupLeader, members } = await createAndPopulateGroup({
|
const { group, groupLeader, members } = await createAndPopulateGroup({
|
||||||
groupDetails: {
|
groupDetails: {
|
||||||
name: 'TestGuild',
|
name: 'TestGuild',
|
||||||
type: 'guild',
|
type: 'guild',
|
||||||
privacy: 'public',
|
privacy: 'private',
|
||||||
},
|
},
|
||||||
members: 1,
|
members: 1,
|
||||||
|
upgradeToGroupPlan: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
publicGuild = group;
|
groupPlan = group;
|
||||||
groupData = {
|
groupData = {
|
||||||
_id: publicGuild._id,
|
_id: groupPlan._id,
|
||||||
categories: [],
|
categories: [],
|
||||||
id: publicGuild._id,
|
id: groupPlan._id,
|
||||||
type: publicGuild.type,
|
type: groupPlan.type,
|
||||||
privacy: publicGuild.privacy,
|
privacy: groupPlan.privacy,
|
||||||
name: publicGuild.name,
|
name: groupPlan.name,
|
||||||
summary: publicGuild.name,
|
summary: groupPlan.name,
|
||||||
leader: publicGuild.leader._id,
|
leader: groupPlan.leader._id,
|
||||||
};
|
};
|
||||||
|
|
||||||
user = groupLeader;
|
user = groupLeader;
|
||||||
userData = {
|
userData = {
|
||||||
_id: publicGuild.leader._id,
|
_id: groupPlan.leader._id,
|
||||||
id: publicGuild.leader._id,
|
id: groupPlan.leader._id,
|
||||||
profile: { name: user.profile.name },
|
profile: { name: user.profile.name },
|
||||||
auth: {
|
auth: {
|
||||||
local: {
|
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
|
member = members[0]; // eslint-disable-line prefer-destructuring
|
||||||
nonMember = await generateUser();
|
nonMember = await generateUser();
|
||||||
|
|
||||||
challenge = await generateChallenge(user, group);
|
challenge = await generateChallenge(user, group);
|
||||||
challenge2 = 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', () => {
|
context('all challenges', () => {
|
||||||
it('should return challenges user has joined', async () => {
|
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 });
|
const foundChallenge = _.find(challenges, { _id: challenge._id });
|
||||||
expect(foundChallenge).to.exist;
|
expect(foundChallenge).to.exist;
|
||||||
@@ -64,11 +83,13 @@ describe('GET challenges/user', () => {
|
|||||||
expect(foundChallenge.group).to.eql(groupData);
|
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 challenges = await nonMember.get('/challenges/user?page=0');
|
||||||
|
|
||||||
const foundChallenge2 = _.find(challenges, { _id: challenge2._id });
|
const foundPublicChallenge = _.find(challenges, { _id: publicChallenge._id });
|
||||||
expect(foundChallenge2).to.not.exist;
|
expect(foundPublicChallenge).to.exist;
|
||||||
|
expect(foundPublicChallenge.leader).to.eql(userData);
|
||||||
|
expect(foundPublicChallenge.group).to.eql(tavernData);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return challenges user has created', async () => {
|
it('should return challenges user has created', async () => {
|
||||||
@@ -100,10 +121,10 @@ describe('GET challenges/user', () => {
|
|||||||
it('should return newest challenges first', async () => {
|
it('should return newest challenges first', async () => {
|
||||||
let challenges = await user.get('/challenges/user?page=0');
|
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);
|
expect(foundChallengeIndex).to.eql(0);
|
||||||
|
|
||||||
const newChallenge = await generateChallenge(user, publicGuild);
|
const newChallenge = await generateChallenge(user, groupPlan);
|
||||||
await user.post(`/challenges/${newChallenge._id}/join`);
|
await user.post(`/challenges/${newChallenge._id}/join`);
|
||||||
|
|
||||||
challenges = await user.get('/challenges/user?page=0');
|
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 () => {
|
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 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;
|
expect(foundChallenge).to.not.exist;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not return challenges user doesn\'t have access to, even with query parameters', async () => {
|
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 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;
|
expect(foundChallenge).to.not.exist;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
context('my challenges', () => {
|
context('my challenges', () => {
|
||||||
it('should return challenges user has joined', async () => {
|
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 });
|
const foundChallenge = _.find(challenges, { _id: challenge._id });
|
||||||
expect(foundChallenge).to.exist;
|
expect(foundChallenge).to.exist;
|
||||||
@@ -177,6 +169,10 @@ describe('GET challenges/user', () => {
|
|||||||
expect(foundChallenge2).to.exist;
|
expect(foundChallenge2).to.exist;
|
||||||
expect(foundChallenge2.leader).to.eql(userData);
|
expect(foundChallenge2.leader).to.eql(userData);
|
||||||
expect(foundChallenge2.group).to.eql(groupData);
|
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 () => {
|
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).to.exist;
|
||||||
expect(foundChallenge2.leader).to.eql(userData);
|
expect(foundChallenge2.leader).to.eql(userData);
|
||||||
expect(foundChallenge2.group).to.eql(groupData);
|
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 () => {
|
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;
|
expect(foundChallenge1).to.not.exist;
|
||||||
const foundChallenge2 = _.find(challenges, { _id: challenge2._id });
|
const foundChallenge2 = _.find(challenges, { _id: challenge2._id });
|
||||||
expect(foundChallenge2).to.not.exist;
|
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 () => {
|
it('should not return challenges in user groups', async () => {
|
||||||
const challenges = await member.get(`/challenges/user?page=0&member=${true}`);
|
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 });
|
const foundChallenge2 = _.find(challenges, { _id: challenge2._id });
|
||||||
expect(foundChallenge2).to.not.exist;
|
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', () => {
|
context('official challenge is present', () => {
|
||||||
let user; let officialChallenge; let unofficialChallenges; let
|
let user; let officialChallenge; let unofficialChallenges; let
|
||||||
publicGuild;
|
group;
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
const { group, groupLeader } = await createAndPopulateGroup({
|
({ group, groupLeader: user } = await createAndPopulateGroup({
|
||||||
groupDetails: {
|
groupDetails: {
|
||||||
name: 'TestGuild',
|
name: 'TestGuild',
|
||||||
summary: 'summary for TestGuild',
|
summary: 'summary for TestGuild',
|
||||||
type: 'guild',
|
type: 'guild',
|
||||||
privacy: 'public',
|
privacy: 'private',
|
||||||
},
|
},
|
||||||
});
|
upgradeToGroupPlan: true,
|
||||||
|
}));
|
||||||
user = groupLeader;
|
|
||||||
publicGuild = group;
|
|
||||||
|
|
||||||
await user.update({
|
await user.update({
|
||||||
'permissions.challengeAdmin': true,
|
'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`);
|
await user.post(`/challenges/${newChallenge._id}/join`);
|
||||||
|
|
||||||
challenges = await user.get('/challenges/user?page=0');
|
challenges = await user.get('/challenges/user?page=0');
|
||||||
@@ -294,9 +298,10 @@ describe('GET challenges/user', () => {
|
|||||||
groupDetails: {
|
groupDetails: {
|
||||||
name: 'TestGuild',
|
name: 'TestGuild',
|
||||||
type: 'guild',
|
type: 'guild',
|
||||||
privacy: 'public',
|
privacy: 'private',
|
||||||
},
|
},
|
||||||
members: 1,
|
members: 1,
|
||||||
|
upgradeToGroupPlan: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
user = groupLeader;
|
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 () => {
|
it('returns error when creating a challenge with summary with greater than MAX_SUMMARY_SIZE_FOR_CHALLENGES characters', 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 () => {
|
|
||||||
const user = await generateUser();
|
const user = await generateUser();
|
||||||
const summary = 'A'.repeat(MAX_SUMMARY_SIZE_FOR_CHALLENGES + 1);
|
const summary = 'A'.repeat(MAX_SUMMARY_SIZE_FOR_CHALLENGES + 1);
|
||||||
const group = createAndPopulateGroup({
|
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 groupLeader;
|
||||||
let group;
|
let group;
|
||||||
let groupMember;
|
let groupMember;
|
||||||
@@ -94,9 +75,11 @@ describe('POST /challenges', () => {
|
|||||||
challenges: true,
|
challenges: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
upgradeToGroupPlan: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
groupLeader = await populatedGroup.groupLeader.sync();
|
groupLeader = await populatedGroup.groupLeader.sync();
|
||||||
|
await groupLeader.update({ permissions: {} });
|
||||||
group = populatedGroup.group;
|
group = populatedGroup.group;
|
||||||
groupMember = populatedGroup.members[0]; // eslint-disable-line prefer-destructuring
|
groupMember = populatedGroup.members[0]; // eslint-disable-line prefer-destructuring
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ describe('PUT /challenges/:challengeId', () => {
|
|||||||
privacy: 'private',
|
privacy: 'private',
|
||||||
},
|
},
|
||||||
members: 1,
|
members: 1,
|
||||||
|
upgradeToGroupPlan: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
privateGuild = group;
|
privateGuild = group;
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { v4 as generateUUID } from 'uuid';
|
import { v4 as generateUUID } from 'uuid';
|
||||||
import {
|
import {
|
||||||
createAndPopulateGroup,
|
createAndPopulateGroup,
|
||||||
generateUser,
|
|
||||||
translate as t,
|
translate as t,
|
||||||
} from '../../../../helpers/api-integration/v3';
|
} from '../../../../helpers/api-integration/v3';
|
||||||
|
|
||||||
@@ -10,27 +9,30 @@ describe('DELETE /groups/:groupId/chat/:chatId', () => {
|
|||||||
admin;
|
admin;
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
const { group, groupLeader } = await createAndPopulateGroup({
|
const { group, groupLeader, members } = await createAndPopulateGroup({
|
||||||
groupDetails: {
|
groupDetails: {
|
||||||
type: 'guild',
|
type: 'guild',
|
||||||
privacy: 'public',
|
privacy: 'private',
|
||||||
},
|
},
|
||||||
leaderDetails: {
|
leaderDetails: {
|
||||||
'auth.timestamps.created': new Date('2022-01-01'),
|
'auth.timestamps.created': new Date('2022-01-01'),
|
||||||
balance: 10,
|
balance: 10,
|
||||||
},
|
},
|
||||||
|
members: 2,
|
||||||
|
upgradeToGroupPlan: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
groupWithChat = group;
|
groupWithChat = group;
|
||||||
user = groupLeader;
|
user = groupLeader;
|
||||||
message = await user.post(`/groups/${groupWithChat._id}/chat`, { message: 'Some message' });
|
message = await user.post(`/groups/${groupWithChat._id}/chat`, { message: 'Some message' });
|
||||||
message = message.message;
|
message = message.message;
|
||||||
userThatDidNotCreateChat = await generateUser();
|
userThatDidNotCreateChat = members[0]; // eslint-disable-line prefer-destructuring
|
||||||
admin = await generateUser({ 'permissions.moderator': true });
|
admin = members[1]; // eslint-disable-line prefer-destructuring
|
||||||
|
await admin.update({ permissions: { moderator: true } });
|
||||||
});
|
});
|
||||||
|
|
||||||
context('Chat errors', () => {
|
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();
|
const fakeChatId = generateUUID();
|
||||||
await expect(user.del(`/groups/${groupWithChat._id}/chat/${fakeChatId}`)).to.eventually.be.rejected.and.eql({
|
await expect(user.del(`/groups/${groupWithChat._id}/chat/${fakeChatId}`)).to.eventually.be.rejected.and.eql({
|
||||||
code: 404,
|
code: 404,
|
||||||
@@ -56,7 +58,7 @@ describe('DELETE /groups/:groupId/chat/:chatId', () => {
|
|||||||
nextMessage = nextMessage.message;
|
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}`);
|
await user.del(`/groups/${groupWithChat._id}/chat/${nextMessage.id}`);
|
||||||
|
|
||||||
const returnedMessages = await user.get(`/groups/${groupWithChat._id}/chat/`);
|
const returnedMessages = await user.get(`/groups/${groupWithChat._id}/chat/`);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
generateUser,
|
generateUser,
|
||||||
generateGroup,
|
createAndPopulateGroup,
|
||||||
translate as t,
|
translate as t,
|
||||||
} from '../../../../helpers/api-integration/v3';
|
} from '../../../../helpers/api-integration/v3';
|
||||||
|
|
||||||
@@ -11,48 +11,22 @@ describe('GET /groups/:groupId/chat', () => {
|
|||||||
user = await generateUser();
|
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', () => {
|
context('private Guild', () => {
|
||||||
let group;
|
let group;
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
const leader = await generateUser({ balance: 2 });
|
({ group } = await createAndPopulateGroup({
|
||||||
|
groupDetails: {
|
||||||
group = await generateGroup(leader, {
|
|
||||||
name: 'test group',
|
name: 'test group',
|
||||||
type: 'guild',
|
type: 'guild',
|
||||||
privacy: 'private',
|
privacy: 'private',
|
||||||
}, {
|
},
|
||||||
|
members: 1,
|
||||||
|
upgradeToGroupPlan: true,
|
||||||
chat: [
|
chat: [
|
||||||
'Hello',
|
'Hello',
|
||||||
'Welcome to the Guild',
|
'Welcome to the Guild',
|
||||||
],
|
],
|
||||||
});
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns error if user is not member of requested private group', async () => {
|
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 moment from 'moment';
|
||||||
import nconf from 'nconf';
|
|
||||||
import { IncomingWebhook } from '@slack/webhook';
|
import { IncomingWebhook } from '@slack/webhook';
|
||||||
import {
|
import {
|
||||||
generateUser,
|
createAndPopulateGroup,
|
||||||
translate as t,
|
translate as t,
|
||||||
} from '../../../../helpers/api-integration/v3';
|
} from '../../../../helpers/api-integration/v3';
|
||||||
|
|
||||||
const BASE_URL = nconf.get('BASE_URL');
|
|
||||||
|
|
||||||
describe('POST /chat/:chatId/flag', () => {
|
describe('POST /chat/:chatId/flag', () => {
|
||||||
let user; let admin; let anotherUser; let newUser; let
|
let user; let admin; let anotherUser; let newUser; let
|
||||||
group;
|
group; let members; let userToDelete;
|
||||||
const TEST_MESSAGE = 'Test Message';
|
const TEST_MESSAGE = 'Test Message';
|
||||||
const USER_AGE_FOR_FLAGGING = 3;
|
const USER_AGE_FOR_FLAGGING = 3;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
user = await generateUser({ balance: 1, 'auth.timestamps.created': moment().subtract(USER_AGE_FOR_FLAGGING + 1, 'days').toDate() });
|
({ group, groupLeader: user, members } = await createAndPopulateGroup({
|
||||||
admin = await generateUser({ balance: 1, 'permissions.moderator': true });
|
groupDetails: {
|
||||||
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 = await user.post('/groups', {
|
|
||||||
name: 'Test Guild',
|
name: 'Test Guild',
|
||||||
type: 'guild',
|
type: 'guild',
|
||||||
privacy: 'public',
|
privacy: 'private',
|
||||||
|
},
|
||||||
|
leaderDetails: {
|
||||||
|
'auth.timestamps.created': moment().subtract(USER_AGE_FOR_FLAGGING + 1, 'days').toDate(),
|
||||||
|
},
|
||||||
|
members: 4,
|
||||||
|
upgradeToGroupPlan: true,
|
||||||
|
}));
|
||||||
|
|
||||||
|
[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(() => {
|
afterEach(() => {
|
||||||
@@ -69,8 +79,8 @@ describe('POST /chat/:chatId/flag', () => {
|
|||||||
fallback: 'Flag Message',
|
fallback: 'Flag Message',
|
||||||
color: 'danger',
|
color: 'danger',
|
||||||
author_name: `@${anotherUser.auth.local.username} ${anotherUser.profile.name} (${anotherUser.auth.local.email}; ${anotherUser._id})\n${timestamp}`,
|
author_name: `@${anotherUser.auth.local.username} ${anotherUser.profile.name} (${anotherUser.auth.local.email}; ${anotherUser._id})\n${timestamp}`,
|
||||||
title: 'Flag in Test Guild',
|
title: 'Flag in Test Guild - (private guild)',
|
||||||
title_link: `${BASE_URL}/groups/guild/${group._id}`,
|
title_link: undefined,
|
||||||
text: TEST_MESSAGE,
|
text: TEST_MESSAGE,
|
||||||
footer: `<https://habitrpg.github.io/flag-o-rama/?groupId=${group._id}&chatId=${message.id}|Flag this message.>`,
|
footer: `<https://habitrpg.github.io/flag-o-rama/?groupId=${group._id}&chatId=${message.id}|Flag this message.>`,
|
||||||
mrkdwn_in: [
|
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 () => {
|
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',
|
fallback: 'Flag Message',
|
||||||
color: 'danger',
|
color: 'danger',
|
||||||
author_name: `@${newUser.auth.local.username} ${newUser.profile.name} (${newUser.auth.local.email}; ${newUser._id})\n${timestamp}`,
|
author_name: `@${newUser.auth.local.username} ${newUser.profile.name} (${newUser.auth.local.email}; ${newUser._id})\n${timestamp}`,
|
||||||
title: 'Flag in Test Guild',
|
title: 'Flag in Test Guild - (private guild)',
|
||||||
title_link: `${BASE_URL}/groups/guild/${group._id}`,
|
title_link: undefined,
|
||||||
text: TEST_MESSAGE,
|
text: TEST_MESSAGE,
|
||||||
footer: `<https://habitrpg.github.io/flag-o-rama/?groupId=${group._id}&chatId=${message.id}|Flag this message.> ${automatedComment}`,
|
footer: `<https://habitrpg.github.io/flag-o-rama/?groupId=${group._id}&chatId=${message.id}|Flag this message.> ${automatedComment}`,
|
||||||
mrkdwn_in: [
|
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 () => {
|
it('Flags a chat when the author\'s account was deleted', async () => {
|
||||||
const deletedUser = await generateUser({
|
const { message } = await userToDelete.post(`/groups/${group._id}/chat`, { message: TEST_MESSAGE });
|
||||||
'auth.timestamps.created': new Date('2022-01-01'),
|
await userToDelete.del('/user', {
|
||||||
});
|
|
||||||
const { message } = await deletedUser.post(`/groups/${group._id}/chat`, { message: TEST_MESSAGE });
|
|
||||||
await deletedUser.del('/user', {
|
|
||||||
password: 'password',
|
password: 'password',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -6,27 +6,27 @@ import {
|
|||||||
|
|
||||||
describe('POST /chat/:chatId/like', () => {
|
describe('POST /chat/:chatId/like', () => {
|
||||||
let user;
|
let user;
|
||||||
let groupWithChat;
|
|
||||||
const testMessage = 'Test Message';
|
|
||||||
let anotherUser;
|
let anotherUser;
|
||||||
|
let groupWithChat;
|
||||||
|
let members;
|
||||||
|
const testMessage = 'Test Message';
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
const { group, groupLeader, members } = await createAndPopulateGroup({
|
({ group: groupWithChat, groupLeader: user, members } = await createAndPopulateGroup({
|
||||||
groupDetails: {
|
groupDetails: {
|
||||||
name: 'Test Guild',
|
name: 'Test Guild',
|
||||||
type: 'guild',
|
type: 'guild',
|
||||||
privacy: 'public',
|
privacy: 'private',
|
||||||
},
|
},
|
||||||
members: 1,
|
members: 1,
|
||||||
leaderDetails: {
|
leaderDetails: {
|
||||||
'auth.timestamps.created': new Date('2022-01-01'),
|
'auth.timestamps.created': new Date('2022-01-01'),
|
||||||
balance: 10,
|
balance: 10,
|
||||||
},
|
},
|
||||||
});
|
upgradeToGroupPlan: true,
|
||||||
|
}));
|
||||||
|
|
||||||
user = groupLeader;
|
[anotherUser] = members;
|
||||||
groupWithChat = group;
|
|
||||||
anotherUser = members[0]; // eslint-disable-line prefer-destructuring
|
|
||||||
await anotherUser.update({ 'auth.timestamps.created': new Date('2022-01-01') });
|
await anotherUser.update({ 'auth.timestamps.created': new Date('2022-01-01') });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,41 +1,33 @@
|
|||||||
import { IncomingWebhook } from '@slack/webhook';
|
import { IncomingWebhook } from '@slack/webhook';
|
||||||
import nconf from 'nconf';
|
|
||||||
import { v4 as generateUUID } from 'uuid';
|
import { v4 as generateUUID } from 'uuid';
|
||||||
import {
|
import {
|
||||||
createAndPopulateGroup,
|
createAndPopulateGroup,
|
||||||
generateUser,
|
|
||||||
translate as t,
|
translate as t,
|
||||||
sleep,
|
sleep,
|
||||||
server,
|
server,
|
||||||
} from '../../../../helpers/api-integration/v3';
|
} from '../../../../helpers/api-integration/v3';
|
||||||
import {
|
import {
|
||||||
SPAM_MESSAGE_LIMIT,
|
|
||||||
SPAM_MIN_EXEMPT_CONTRIB_LEVEL,
|
SPAM_MIN_EXEMPT_CONTRIB_LEVEL,
|
||||||
TAVERN_ID,
|
|
||||||
} from '../../../../../website/server/models/group';
|
} 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';
|
import * as email from '../../../../../website/server/libs/email';
|
||||||
|
|
||||||
const BASE_URL = nconf.get('BASE_URL');
|
|
||||||
|
|
||||||
describe('POST /chat', () => {
|
describe('POST /chat', () => {
|
||||||
let user; let groupWithChat; let member; let
|
let user; let groupWithChat; let member; let
|
||||||
additionalMember;
|
additionalMember;
|
||||||
const testMessage = 'Test Message';
|
const testMessage = 'Test Message';
|
||||||
const testBannedWordMessage = 'TESTPLACEHOLDERSWEARWORDHERE';
|
const testBannedWordMessage = 'TESTPLACEHOLDERSWEARWORDHERE';
|
||||||
const testBannedWordMessage1 = 'TESTPLACEHOLDERSWEARWORDHERE1';
|
|
||||||
const testSlurMessage = 'message with TESTPLACEHOLDERSLURWORDHERE';
|
const testSlurMessage = 'message with TESTPLACEHOLDERSLURWORDHERE';
|
||||||
const testSlurMessage1 = 'TESTPLACEHOLDERSLURWORDHERE1';
|
|
||||||
const bannedWordErrorMessage = t('bannedWordUsed', { swearWordsUsed: testBannedWordMessage });
|
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
const { group, groupLeader, members } = await createAndPopulateGroup({
|
const { group, groupLeader, members } = await createAndPopulateGroup({
|
||||||
groupDetails: {
|
groupDetails: {
|
||||||
name: 'Test Guild',
|
name: 'Test Guild',
|
||||||
type: 'guild',
|
type: 'guild',
|
||||||
privacy: 'public',
|
privacy: 'private',
|
||||||
},
|
},
|
||||||
members: 2,
|
members: 2,
|
||||||
|
upgradeToGroupPlan: true,
|
||||||
});
|
});
|
||||||
user = groupLeader;
|
user = groupLeader;
|
||||||
await user.update({
|
await user.update({
|
||||||
@@ -43,8 +35,7 @@ describe('POST /chat', () => {
|
|||||||
'auth.timestamps.created': new Date('2022-01-01'),
|
'auth.timestamps.created': new Date('2022-01-01'),
|
||||||
}); // prevent tests accidentally throwing messageGroupChatSpam
|
}); // prevent tests accidentally throwing messageGroupChatSpam
|
||||||
groupWithChat = group;
|
groupWithChat = group;
|
||||||
member = members[0]; // eslint-disable-line prefer-destructuring
|
[member, additionalMember] = members;
|
||||||
additionalMember = members[1]; // eslint-disable-line prefer-destructuring
|
|
||||||
await member.update({ 'auth.timestamps.created': new Date('2022-01-01') });
|
await member.update({ 'auth.timestamps.created': new Date('2022-01-01') });
|
||||||
await additionalMember.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 });
|
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 () => {
|
it('does not error when chat privileges are revoked when sending a message to a private guild', async () => {
|
||||||
const { group, members } = await createAndPopulateGroup({
|
await member.update({
|
||||||
groupDetails: {
|
|
||||||
name: 'Private Guild',
|
|
||||||
type: 'guild',
|
|
||||||
privacy: 'private',
|
|
||||||
},
|
|
||||||
members: 1,
|
|
||||||
});
|
|
||||||
|
|
||||||
const privateGuildMemberWithChatsRevoked = members[0];
|
|
||||||
await privateGuildMemberWithChatsRevoked.update({
|
|
||||||
'flags.chatRevoked': true,
|
'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;
|
expect(message.message.id).to.exist;
|
||||||
});
|
});
|
||||||
@@ -152,54 +123,12 @@ describe('POST /chat', () => {
|
|||||||
member.update({ 'flags.chatShadowMuted': false });
|
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 () => {
|
it('creates a chat with zero flagCount when sending a message to a private guild', async () => {
|
||||||
const { group, members } = await createAndPopulateGroup({
|
await member.update({
|
||||||
groupDetails: {
|
|
||||||
name: 'Private Guild',
|
|
||||||
type: 'guild',
|
|
||||||
privacy: 'private',
|
|
||||||
},
|
|
||||||
members: 1,
|
|
||||||
});
|
|
||||||
|
|
||||||
const userWithChatShadowMuted = members[0];
|
|
||||||
await userWithChatShadowMuted.update({
|
|
||||||
'flags.chatShadowMuted': true,
|
'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.id).to.exist;
|
||||||
expect(message.message.flagCount).to.eql(0);
|
expect(message.message.flagCount).to.eql(0);
|
||||||
@@ -226,100 +155,9 @@ describe('POST /chat', () => {
|
|||||||
expect(message.message.id).to.exist;
|
expect(message.message.id).to.exist;
|
||||||
expect(message.message.flagCount).to.eql(0);
|
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', () => {
|
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 () => {
|
it('does not error when sending a chat message containing a banned word to a party', async () => {
|
||||||
const { group, members } = await createAndPopulateGroup({
|
const { group, members } = await createAndPopulateGroup({
|
||||||
groupDetails: {
|
groupDetails: {
|
||||||
@@ -336,37 +174,8 @@ describe('POST /chat', () => {
|
|||||||
expect(message.message.id).to.exist;
|
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 () => {
|
it('does not error when sending a chat message containing a banned word to a private guild', async () => {
|
||||||
const { group, members } = await createAndPopulateGroup({
|
const message = await member.post(`/groups/${groupWithChat._id}/chat`, { message: testBannedWordMessage });
|
||||||
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 });
|
|
||||||
|
|
||||||
expect(message.message.id).to.exist;
|
expect(message.message.id).to.exist;
|
||||||
});
|
});
|
||||||
@@ -383,45 +192,6 @@ describe('POST /chat', () => {
|
|||||||
user.update({ 'flags.chatRevoked': false });
|
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 () => {
|
it('allows slurs in private groups', async () => {
|
||||||
const { group, members } = await createAndPopulateGroup({
|
const { group, members } = await createAndPopulateGroup({
|
||||||
groupDetails: {
|
groupDetails: {
|
||||||
@@ -437,28 +207,17 @@ describe('POST /chat', () => {
|
|||||||
|
|
||||||
expect(message.message.id).to.exist;
|
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 () => {
|
it('errors when user account is too young', async () => {
|
||||||
const brandNewUser = await generateUser();
|
await user.update({ 'auth.timestamps.created': new Date() });
|
||||||
await expect(brandNewUser.post('/groups/habitrpg/chat', { message: 'hi im new' }))
|
await expect(user.post(`/groups/${groupWithChat._id}/chat`, { message: 'hi im new' }))
|
||||||
.to.eventually.be.rejected.and.eql({
|
.to.eventually.be.rejected.and.eql({
|
||||||
code: 400,
|
code: 400,
|
||||||
error: 'BadRequest',
|
error: 'BadRequest',
|
||||||
message: t('chatTemporarilyUnavailable'),
|
message: t('chatTemporarilyUnavailable'),
|
||||||
});
|
});
|
||||||
|
await user.update({ 'auth.timestamps.created': new Date('2022-01-01') });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('creates a chat', async () => {
|
it('creates a chat', async () => {
|
||||||
@@ -519,54 +278,42 @@ describe('POST /chat', () => {
|
|||||||
const mount = 'test-mount';
|
const mount = 'test-mount';
|
||||||
const pet = 'test-pet';
|
const pet = 'test-pet';
|
||||||
const style = 'test-style';
|
const style = 'test-style';
|
||||||
const userWithStyle = await generateUser({
|
await user.update({
|
||||||
'items.currentMount': mount,
|
'items.currentMount': mount,
|
||||||
'items.currentPet': pet,
|
'items.currentPet': pet,
|
||||||
'preferences.style': style,
|
'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.id).to.exist;
|
||||||
expect(message.message.userStyles.items.currentMount).to.eql(userWithStyle.items.currentMount);
|
expect(message.message.userStyles.items.currentMount).to.eql(user.items.currentMount);
|
||||||
expect(message.message.userStyles.items.currentPet).to.eql(userWithStyle.items.currentPet);
|
expect(message.message.userStyles.items.currentPet).to.eql(user.items.currentPet);
|
||||||
expect(message.message.userStyles.preferences.style).to.eql(userWithStyle.preferences.style);
|
expect(message.message.userStyles.preferences.style).to.eql(user.preferences.style);
|
||||||
expect(message.message.userStyles.preferences.hair).to.eql(userWithStyle.preferences.hair);
|
expect(message.message.userStyles.preferences.hair).to.eql(user.preferences.hair);
|
||||||
expect(message.message.userStyles.preferences.skin).to.eql(userWithStyle.preferences.skin);
|
expect(message.message.userStyles.preferences.skin).to.eql(user.preferences.skin);
|
||||||
expect(message.message.userStyles.preferences.shirt).to.eql(userWithStyle.preferences.shirt);
|
expect(message.message.userStyles.preferences.shirt).to.eql(user.preferences.shirt);
|
||||||
expect(message.message.userStyles.preferences.chair).to.eql(userWithStyle.preferences.chair);
|
expect(message.message.userStyles.preferences.chair).to.eql(user.preferences.chair);
|
||||||
expect(message.message.userStyles.preferences.background)
|
expect(message.message.userStyles.preferences.background)
|
||||||
.to.eql(userWithStyle.preferences.background);
|
.to.eql(user.preferences.background);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('creates equipped to user styles', async () => {
|
it('creates equipped to user styles', async () => {
|
||||||
const userWithStyle = await generateUser({
|
const message = await user.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage });
|
||||||
'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 });
|
|
||||||
|
|
||||||
expect(message.message.id).to.exist;
|
expect(message.message.id).to.exist;
|
||||||
expect(message.message.userStyles.items.gear.equipped)
|
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;
|
expect(message.message.userStyles.items.gear.costume).to.not.exist;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('creates costume to user styles', async () => {
|
it('creates costume to user styles', async () => {
|
||||||
const userWithStyle = await generateUser({
|
await user.update({ 'preferences.costume': true });
|
||||||
'preferences.costume': true,
|
|
||||||
'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.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;
|
expect(message.message.userStyles.items.gear.equipped).to.not.exist;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -576,12 +323,11 @@ describe('POST /chat', () => {
|
|||||||
tier: 800,
|
tier: 800,
|
||||||
tokensApplied: true,
|
tokensApplied: true,
|
||||||
};
|
};
|
||||||
const backer = await generateUser({
|
await user.update({
|
||||||
backer: backerInfo,
|
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;
|
const messageBackerInfo = message.message.backer;
|
||||||
|
|
||||||
expect(messageBackerInfo.npc).to.equal(backerInfo.npc);
|
expect(messageBackerInfo.npc).to.equal(backerInfo.npc);
|
||||||
@@ -661,43 +407,5 @@ describe('POST /chat', () => {
|
|||||||
expect(memberWithNotification.newMessages[`${group._id}`]).to.exist;
|
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;
|
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({
|
const { group, groupLeader, members } = await createAndPopulateGroup({
|
||||||
groupDetails: {
|
groupDetails: {
|
||||||
type: 'guild',
|
type: 'guild',
|
||||||
privacy: 'public',
|
privacy: 'private',
|
||||||
},
|
},
|
||||||
members: 1,
|
members: 1,
|
||||||
leaderDetails: {
|
leaderDetails: {
|
||||||
'auth.timestamps.created': new Date('2022-01-01'),
|
'auth.timestamps.created': new Date('2022-01-01'),
|
||||||
balance: 10,
|
balance: 10,
|
||||||
},
|
},
|
||||||
|
upgradeToGroupPlan: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
guild = group;
|
guild = group;
|
||||||
guildLeader = groupLeader;
|
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 = await guildLeader.post(`/groups/${guild._id}/chat`, { message: 'Some guild message' });
|
||||||
guildMessage = guildMessage.message;
|
guildMessage = guildMessage.message;
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import moment from 'moment';
|
|||||||
import { v4 as generateUUID } from 'uuid';
|
import { v4 as generateUUID } from 'uuid';
|
||||||
import {
|
import {
|
||||||
createAndPopulateGroup,
|
createAndPopulateGroup,
|
||||||
generateUser,
|
|
||||||
translate as t,
|
translate as t,
|
||||||
} from '../../../../helpers/api-integration/v3';
|
} from '../../../../helpers/api-integration/v3';
|
||||||
import config from '../../../../../config.json';
|
import config from '../../../../../config.json';
|
||||||
@@ -13,21 +12,24 @@ describe('POST /groups/:id/chat/:id/clearflags', () => {
|
|||||||
admin;
|
admin;
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
const { group, groupLeader } = await createAndPopulateGroup({
|
const { group, groupLeader, members } = await createAndPopulateGroup({
|
||||||
groupDetails: {
|
groupDetails: {
|
||||||
type: 'guild',
|
type: 'guild',
|
||||||
privacy: 'public',
|
privacy: 'private',
|
||||||
},
|
},
|
||||||
leaderDetails: {
|
leaderDetails: {
|
||||||
'auth.timestamps.created': new Date('2022-01-01'),
|
'auth.timestamps.created': new Date('2022-01-01'),
|
||||||
balance: 10,
|
balance: 10,
|
||||||
},
|
},
|
||||||
|
upgradeToGroupPlan: true,
|
||||||
|
members: 2,
|
||||||
});
|
});
|
||||||
|
|
||||||
groupWithChat = group;
|
groupWithChat = group;
|
||||||
author = groupLeader;
|
author = groupLeader;
|
||||||
nonAdmin = await generateUser({ 'auth.timestamps.created': moment().subtract(USER_AGE_FOR_FLAGGING + 1, 'days').toDate() });
|
[nonAdmin, admin] = members;
|
||||||
admin = await generateUser({ 'permissions.moderator': true });
|
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 = await author.post(`/groups/${groupWithChat._id}/chat`, { message: 'Some message' });
|
||||||
message = message.message;
|
message = message.message;
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
generateUser,
|
createAndPopulateGroup,
|
||||||
generateGroup,
|
|
||||||
} from '../../../../helpers/api-integration/v3';
|
} from '../../../../helpers/api-integration/v3';
|
||||||
|
|
||||||
describe('GET /group-plans', () => {
|
describe('GET /group-plans', () => {
|
||||||
@@ -8,20 +7,15 @@ describe('GET /group-plans', () => {
|
|||||||
let groupPlan;
|
let groupPlan;
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
user = await generateUser({ balance: 4 });
|
({ group: groupPlan, groupLeader: user } = await createAndPopulateGroup({
|
||||||
groupPlan = await generateGroup(user,
|
groupDetails: {
|
||||||
{
|
name: 'group plan - is member',
|
||||||
name: 'public guild - is member',
|
|
||||||
type: 'guild',
|
type: 'guild',
|
||||||
privacy: 'public',
|
privacy: 'private',
|
||||||
},
|
},
|
||||||
{
|
upgradeToGroupPlan: true,
|
||||||
purchased: {
|
leaderDetails: { balance: 4 },
|
||||||
plan: {
|
}));
|
||||||
customerId: 'existings',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns group plans for the user', async () => {
|
it('returns group plans for the user', async () => {
|
||||||
|
|||||||
@@ -1,70 +1,63 @@
|
|||||||
import {
|
import {
|
||||||
generateUser,
|
createAndPopulateGroup,
|
||||||
resetHabiticaDB,
|
resetHabiticaDB,
|
||||||
generateGroup,
|
generateGroup,
|
||||||
translate as t,
|
translate as t,
|
||||||
} from '../../../../helpers/api-integration/v3';
|
} from '../../../../helpers/api-integration/v3';
|
||||||
import {
|
|
||||||
TAVERN_ID,
|
|
||||||
} from '../../../../../website/server/models/group';
|
|
||||||
import apiError from '../../../../../website/server/libs/apiError';
|
|
||||||
|
|
||||||
describe('GET /groups', () => {
|
describe('GET /groups', () => {
|
||||||
let user;
|
let user; let leader; let members;
|
||||||
let userInGuild;
|
let secondGroup; let secondLeader;
|
||||||
const NUMBER_OF_PUBLIC_GUILDS = 2;
|
const NUMBER_OF_USERS_PRIVATE_GUILDS = 2;
|
||||||
const NUMBER_OF_PUBLIC_GUILDS_USER_IS_LEADER = 2;
|
const NUMBER_OF_GROUPS_USER_CAN_VIEW = 3;
|
||||||
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;
|
|
||||||
const categories = [{
|
const categories = [{
|
||||||
slug: 'newCat',
|
slug: 'newCat',
|
||||||
name: 'New Category',
|
name: 'New Category',
|
||||||
}];
|
}];
|
||||||
let publicGuildNotMember;
|
|
||||||
let privateGuildUserIsMemberOf;
|
let privateGuildUserIsMemberOf;
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
await resetHabiticaDB();
|
await resetHabiticaDB();
|
||||||
|
|
||||||
const leader = await generateUser({ balance: 10 });
|
({
|
||||||
user = await generateUser({ balance: 4 });
|
group: privateGuildUserIsMemberOf,
|
||||||
|
groupLeader: leader,
|
||||||
const publicGuildUserIsMemberOf = await generateGroup(leader, {
|
members,
|
||||||
name: 'public guild - is member',
|
} = await createAndPopulateGroup({
|
||||||
type: 'guild',
|
groupDetails: {
|
||||||
privacy: 'public',
|
|
||||||
summary: 'ohayou kombonwa',
|
|
||||||
description: 'oyasumi',
|
|
||||||
});
|
|
||||||
await leader.post(`/groups/${publicGuildUserIsMemberOf._id}/invite`, { uuids: [user._id] });
|
|
||||||
await user.post(`/groups/${publicGuildUserIsMemberOf._id}/join`);
|
|
||||||
|
|
||||||
userInGuild = await generateUser({ guilds: [publicGuildUserIsMemberOf._id] });
|
|
||||||
|
|
||||||
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',
|
name: 'private guild - is member',
|
||||||
type: 'guild',
|
type: 'guild',
|
||||||
privacy: 'private',
|
privacy: 'private',
|
||||||
categories,
|
categories,
|
||||||
});
|
},
|
||||||
await leader.post(`/groups/${privateGuildUserIsMemberOf._id}/invite`, { uuids: [user._id] });
|
leaderDetails: {
|
||||||
await user.post(`/groups/${privateGuildUserIsMemberOf._id}/join`);
|
balance: 10,
|
||||||
|
},
|
||||||
|
members: 1,
|
||||||
|
upgradeToGroupPlan: true,
|
||||||
|
}));
|
||||||
|
[user] = members;
|
||||||
|
await user.update({ balance: 4 });
|
||||||
|
|
||||||
await generateGroup(leader, {
|
({ group: secondGroup, groupLeader: secondLeader } = await createAndPopulateGroup({
|
||||||
|
groupDetails: {
|
||||||
|
name: 'c++ coders',
|
||||||
|
type: 'guild',
|
||||||
|
privacy: 'private',
|
||||||
|
},
|
||||||
|
upgradeToGroupPlan: true,
|
||||||
|
}));
|
||||||
|
|
||||||
|
await secondLeader.post(`/groups/${secondGroup._id}/invite`, { uuids: [user._id] });
|
||||||
|
await user.post(`/groups/${secondGroup._id}/join`);
|
||||||
|
|
||||||
|
await createAndPopulateGroup({
|
||||||
|
groupDetails: {
|
||||||
name: 'private guild - is not member',
|
name: 'private guild - is not member',
|
||||||
type: 'guild',
|
type: 'guild',
|
||||||
privacy: 'private',
|
privacy: 'private',
|
||||||
|
},
|
||||||
|
upgradeToGroupPlan: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
await generateGroup(leader, {
|
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 () => {
|
it('returns only the user\'s party when party passed in as query', async () => {
|
||||||
await expect(user.get('/groups?type=party'))
|
await expect(user.get('/groups?type=party'))
|
||||||
.to.eventually.have.a.lengthOf(1)
|
.to.eventually.have.a.lengthOf(1)
|
||||||
.and.to.have.nested.property('[0]');
|
.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 () => {
|
it('returns all the user\'s guilds when guilds passed in as query', async () => {
|
||||||
await expect(user.get('/groups?type=guilds'))
|
await expect(user.get('/groups?type=guilds'))
|
||||||
.to.eventually.have.a
|
.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 () => {
|
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 () => {
|
it('returns a list of groups user has access to', async () => {
|
||||||
await expect(user.get('/groups?type=privateGuilds,publicGuilds,party,tavern'))
|
await expect(user.get('/groups?type=privateGuilds,party'))
|
||||||
.to.eventually.have.lengthOf(NUMBER_OF_GROUPS_USER_CAN_VIEW - 1); // -1 for no Tavern.
|
.to.eventually.have.lengthOf(NUMBER_OF_GROUPS_USER_CAN_VIEW);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns a list of groups user has access to', async () => {
|
describe('filters', () => {
|
||||||
const group = await generateGroup(user, {
|
it('returns private guilds filtered by category', async () => {
|
||||||
name: 'c++ coders',
|
const guilds = await user.get(`/groups?type=privateGuilds&categories=${categories[0].slug}`);
|
||||||
type: 'guild',
|
|
||||||
privacy: 'public',
|
expect(guilds[0]._id).to.equal(privateGuildUserIsMemberOf._id);
|
||||||
});
|
});
|
||||||
|
|
||||||
// search for 'c++ coders'
|
it('filters private guilds by size', async () => {
|
||||||
await expect(user.get('/groups?type=publicGuilds&paginate=true&page=0&search=c%2B%2B+coders'))
|
const guilds = await user.get('/groups?type=privateGuilds&minMemberCount=3');
|
||||||
.to.eventually.have.lengthOf(1)
|
|
||||||
.and.to.have.nested.property('[0]')
|
expect(guilds.length).to.equal(0);
|
||||||
.and.to.have.property('_id', group._id);
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import {
|
|||||||
generateUser,
|
generateUser,
|
||||||
generateGroup,
|
generateGroup,
|
||||||
translate as t,
|
translate as t,
|
||||||
|
createAndPopulateGroup,
|
||||||
} from '../../../../helpers/api-integration/v3';
|
} from '../../../../helpers/api-integration/v3';
|
||||||
|
|
||||||
describe('GET /groups/:groupId/invites', () => {
|
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 () => {
|
it('returns only first 30 invites by default (req.query.limit not specified)', async () => {
|
||||||
const leader = await generateUser({ balance: 4 });
|
const { group, groupLeader: leader } = await createAndPopulateGroup({
|
||||||
const group = await generateGroup(leader, { type: 'guild', privacy: 'public', name: generateUUID() });
|
groupDetails: {
|
||||||
|
type: 'guild',
|
||||||
const invitesToGenerate = [];
|
privacy: 'private',
|
||||||
for (let i = 0; i < 31; i += 1) {
|
name: generateUUID(),
|
||||||
invitesToGenerate.push(generateUser());
|
},
|
||||||
}
|
leaderDetails: { balance: 4 },
|
||||||
const generatedInvites = await Promise.all(invitesToGenerate);
|
invites: 31,
|
||||||
await leader.post(`/groups/${group._id}/invite`, { uuids: generatedInvites.map(invite => invite._id) });
|
upgradeToGroupPlan: true,
|
||||||
|
});
|
||||||
|
|
||||||
const res = await leader.get(`/groups/${group._id}/invites`);
|
const res = await leader.get(`/groups/${group._id}/invites`);
|
||||||
expect(res.length).to.equal(30);
|
expect(res.length).to.equal(30);
|
||||||
@@ -90,8 +92,16 @@ describe('GET /groups/:groupId/invites', () => {
|
|||||||
}).timeout(10000);
|
}).timeout(10000);
|
||||||
|
|
||||||
it('returns an error if req.query.limit is over 60', async () => {
|
it('returns an error if req.query.limit is over 60', async () => {
|
||||||
const leader = await generateUser({ balance: 4 });
|
const { group, groupLeader: leader } = await createAndPopulateGroup({
|
||||||
const group = await generateGroup(leader, { type: 'guild', privacy: 'public', name: generateUUID() });
|
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({
|
await expect(leader.get(`/groups/${group._id}/invites?limit=61`)).to.eventually.be.rejected.and.eql({
|
||||||
code: 400,
|
code: 400,
|
||||||
@@ -101,8 +111,16 @@ describe('GET /groups/:groupId/invites', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('returns an error if req.query.limit is under 1', async () => {
|
it('returns an error if req.query.limit is under 1', async () => {
|
||||||
const leader = await generateUser({ balance: 4 });
|
const { group, groupLeader: leader } = await createAndPopulateGroup({
|
||||||
const group = await generateGroup(leader, { type: 'guild', privacy: 'public', name: generateUUID() });
|
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({
|
await expect(leader.get(`/groups/${group._id}/invites?limit=-1`)).to.eventually.be.rejected.and.eql({
|
||||||
code: 400,
|
code: 400,
|
||||||
@@ -112,8 +130,16 @@ describe('GET /groups/:groupId/invites', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('returns an error if req.query.limit is not an integer', async () => {
|
it('returns an error if req.query.limit is not an integer', async () => {
|
||||||
const leader = await generateUser({ balance: 4 });
|
const { group, groupLeader: leader } = await createAndPopulateGroup({
|
||||||
const group = await generateGroup(leader, { type: 'guild', privacy: 'public', name: generateUUID() });
|
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({
|
await expect(leader.get(`/groups/${group._id}/invites?limit=1.3`)).to.eventually.be.rejected.and.eql({
|
||||||
code: 400,
|
code: 400,
|
||||||
@@ -123,15 +149,16 @@ describe('GET /groups/:groupId/invites', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('returns up to 60 invites when req.query.limit is specified', async () => {
|
it('returns up to 60 invites when req.query.limit is specified', async () => {
|
||||||
const leader = await generateUser({ balance: 4 });
|
const { group, groupLeader: leader } = await createAndPopulateGroup({
|
||||||
const group = await generateGroup(leader, { type: 'guild', privacy: 'public', name: generateUUID() });
|
groupDetails: {
|
||||||
|
type: 'guild',
|
||||||
const invitesToGenerate = [];
|
privacy: 'private',
|
||||||
for (let i = 0; i < 31; i += 1) {
|
name: generateUUID(),
|
||||||
invitesToGenerate.push(generateUser());
|
},
|
||||||
}
|
leaderDetails: { balance: 4 },
|
||||||
const generatedInvites = await Promise.all(invitesToGenerate);
|
invites: 31,
|
||||||
await leader.post(`/groups/${group._id}/invite`, { uuids: generatedInvites.map(invite => invite._id) });
|
upgradeToGroupPlan: true,
|
||||||
|
});
|
||||||
|
|
||||||
let res = await leader.get(`/groups/${group._id}/invites?limit=14`);
|
let res = await leader.get(`/groups/${group._id}/invites?limit=14`);
|
||||||
expect(res.length).to.equal(14);
|
expect(res.length).to.equal(14);
|
||||||
@@ -149,17 +176,20 @@ describe('GET /groups/:groupId/invites', () => {
|
|||||||
}).timeout(30000);
|
}).timeout(30000);
|
||||||
|
|
||||||
it('supports using req.query.lastId to get more invites', async function test () {
|
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
|
this.timeout(30000); // @TODO: times out after 8 seconds
|
||||||
const leader = await generateUser({ balance: 4 });
|
({ group, groupLeader: user, invitees } = await createAndPopulateGroup({
|
||||||
const group = await generateGroup(leader, { type: 'guild', privacy: 'public', name: generateUUID() });
|
groupDetails: {
|
||||||
|
type: 'guild',
|
||||||
|
privacy: 'private',
|
||||||
|
name: generateUUID(),
|
||||||
|
},
|
||||||
|
leaderDetails: { balance: 4 },
|
||||||
|
invites: 32,
|
||||||
|
upgradeToGroupPlan: true,
|
||||||
|
}));
|
||||||
|
|
||||||
const invitesToGenerate = [];
|
const expectedIds = invitees.map(generatedInvite => generatedInvite._id);
|
||||||
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 res = await user.get(`/groups/${group._id}/invites`);
|
const res = await user.get(`/groups/${group._id}/invites`);
|
||||||
expect(res.length).to.equal(30);
|
expect(res.length).to.equal(30);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { v4 as generateUUID } from 'uuid';
|
import { v4 as generateUUID } from 'uuid';
|
||||||
import {
|
import {
|
||||||
|
createAndPopulateGroup,
|
||||||
generateUser,
|
generateUser,
|
||||||
generateGroup,
|
generateGroup,
|
||||||
translate as t,
|
translate as t,
|
||||||
@@ -75,7 +76,15 @@ describe('GET /groups/:groupId/members', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('req.query.includeAllPublicFields === true works with guilds', async () => {
|
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`);
|
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
|
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 () {
|
it('supports using req.query.lastId to get more members', async function test () {
|
||||||
this.timeout(30000); // @TODO: times out after 8 seconds
|
this.timeout(30000); // @TODO: times out after 8 seconds
|
||||||
const leader = await generateUser({ balance: 4 });
|
const { group, groupLeader: leader, members: generatedUsers } = await createAndPopulateGroup({
|
||||||
const group = await generateGroup(leader, { type: 'guild', privacy: 'public', name: generateUUID() });
|
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 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);
|
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);
|
expect(res2.length).to.equal(28);
|
||||||
|
|
||||||
const resIds = res.concat(res2).map(member => member._id);
|
const resIds = res.concat(res2).map(member => member._id);
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import {
|
|||||||
|
|
||||||
describe('GET /groups/:id', () => {
|
describe('GET /groups/:id', () => {
|
||||||
const typesOfGroups = {};
|
const typesOfGroups = {};
|
||||||
typesOfGroups['public guild'] = { type: 'guild', privacy: 'public' };
|
|
||||||
typesOfGroups['private guild'] = { type: 'guild', privacy: 'private' };
|
typesOfGroups['private guild'] = { type: 'guild', privacy: 'private' };
|
||||||
typesOfGroups.party = { type: 'party', privacy: 'private' };
|
typesOfGroups.party = { type: 'party', privacy: 'private' };
|
||||||
|
|
||||||
@@ -24,10 +23,11 @@ describe('GET /groups/:id', () => {
|
|||||||
const groupData = await createAndPopulateGroup({
|
const groupData = await createAndPopulateGroup({
|
||||||
members: 30,
|
members: 30,
|
||||||
groupDetails,
|
groupDetails,
|
||||||
|
upgradeToGroupPlan: groupDetails.type === 'guild',
|
||||||
});
|
});
|
||||||
|
|
||||||
leader = groupData.groupLeader;
|
leader = groupData.groupLeader;
|
||||||
member = groupData.members[0]; // eslint-disable-line prefer-destructuring
|
[member] = groupData.members;
|
||||||
createdGroup = groupData.group;
|
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', () => {
|
context('Non-member of a private guild', () => {
|
||||||
let nonMember; let
|
let nonMember; let
|
||||||
createdGroup;
|
createdGroup;
|
||||||
@@ -89,6 +61,7 @@ describe('GET /groups/:id', () => {
|
|||||||
type: 'guild',
|
type: 'guild',
|
||||||
privacy: 'private',
|
privacy: 'private',
|
||||||
},
|
},
|
||||||
|
upgradeToGroupPlan: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
createdGroup = groupData.group;
|
createdGroup = groupData.group;
|
||||||
@@ -218,7 +191,7 @@ describe('GET /groups/:id', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
context('Flagged messages', () => {
|
context('Flagged messages', () => {
|
||||||
let group;
|
let group; let members;
|
||||||
|
|
||||||
const chat1 = {
|
const chat1 = {
|
||||||
id: 'chat1',
|
id: 'chat1',
|
||||||
@@ -268,7 +241,7 @@ describe('GET /groups/:id', () => {
|
|||||||
groupDetails: {
|
groupDetails: {
|
||||||
name: 'test guild',
|
name: 'test guild',
|
||||||
type: 'guild',
|
type: 'guild',
|
||||||
privacy: 'public',
|
privacy: 'private',
|
||||||
chat: [
|
chat: [
|
||||||
chat1,
|
chat1,
|
||||||
chat2,
|
chat2,
|
||||||
@@ -277,9 +250,11 @@ describe('GET /groups/:id', () => {
|
|||||||
chat5,
|
chat5,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
members: 1,
|
||||||
|
upgradeToGroupPlan: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
group = groupData.group;
|
({ group, members } = groupData);
|
||||||
|
|
||||||
await group.addChat([chat1, chat2, chat3, chat4, chat5]);
|
await group.addChat([chat1, chat2, chat3, chat4, chat5]);
|
||||||
});
|
});
|
||||||
@@ -287,8 +262,8 @@ describe('GET /groups/:id', () => {
|
|||||||
context('non-admin', () => {
|
context('non-admin', () => {
|
||||||
let nonAdmin;
|
let nonAdmin;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(() => {
|
||||||
nonAdmin = await generateUser();
|
[nonAdmin] = members;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not include messages with a flag count of 2 or greater', async () => {
|
it('does not include messages with a flag count of 2 or greater', async () => {
|
||||||
@@ -314,9 +289,8 @@ describe('GET /groups/:id', () => {
|
|||||||
let admin;
|
let admin;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
admin = await generateUser({
|
[admin] = members;
|
||||||
'permissions.moderator': true,
|
await admin.update({ permissions: { moderator: true } });
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('includes all messages', async () => {
|
it('includes all messages', async () => {
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import {
|
|||||||
generateUser,
|
generateUser,
|
||||||
translate as t,
|
translate as t,
|
||||||
} from '../../../../helpers/api-integration/v3';
|
} 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';
|
import { MAX_SUMMARY_SIZE_FOR_GUILDS } from '../../../../../website/common/script/constants';
|
||||||
|
|
||||||
describe('POST /group', () => {
|
describe('POST /group', () => {
|
||||||
@@ -35,8 +34,8 @@ describe('POST /group', () => {
|
|||||||
|
|
||||||
it('sets the group leader to the user who created the group', async () => {
|
it('sets the group leader to the user who created the group', async () => {
|
||||||
const group = await user.post('/groups', {
|
const group = await user.post('/groups', {
|
||||||
name: 'Test Public Guild',
|
name: 'Test Party',
|
||||||
type: 'guild',
|
type: 'party',
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(group.leader).to.eql({
|
expect(group.leader).to.eql({
|
||||||
@@ -51,7 +50,7 @@ describe('POST /group', () => {
|
|||||||
const name = 'Test Group';
|
const name = 'Test Group';
|
||||||
const group = await user.post('/groups', {
|
const group = await user.post('/groups', {
|
||||||
name,
|
name,
|
||||||
type: 'guild',
|
type: 'party',
|
||||||
});
|
});
|
||||||
|
|
||||||
const updatedGroup = await user.get(`/groups/${group._id}`);
|
const updatedGroup = await user.get(`/groups/${group._id}`);
|
||||||
@@ -64,7 +63,7 @@ describe('POST /group', () => {
|
|||||||
const summary = 'Test Summary';
|
const summary = 'Test Summary';
|
||||||
const group = await user.post('/groups', {
|
const group = await user.post('/groups', {
|
||||||
name,
|
name,
|
||||||
type: 'guild',
|
type: 'party',
|
||||||
summary,
|
summary,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -78,7 +77,7 @@ describe('POST /group', () => {
|
|||||||
const summary = 'A'.repeat(MAX_SUMMARY_SIZE_FOR_GUILDS + 1);
|
const summary = 'A'.repeat(MAX_SUMMARY_SIZE_FOR_GUILDS + 1);
|
||||||
await expect(user.post('/groups', {
|
await expect(user.post('/groups', {
|
||||||
name,
|
name,
|
||||||
type: 'guild',
|
type: 'party',
|
||||||
summary,
|
summary,
|
||||||
})).to.eventually.be.rejected.and.eql({
|
})).to.eventually.be.rejected.and.eql({
|
||||||
code: 400,
|
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', () => {
|
context('Parties', () => {
|
||||||
const partyName = 'Test Party';
|
const partyName = 'Test Party';
|
||||||
const partyType = 'party';
|
const partyType = 'party';
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { v4 as generateUUID } from 'uuid';
|
|||||||
import {
|
import {
|
||||||
generateUser,
|
generateUser,
|
||||||
createAndPopulateGroup,
|
createAndPopulateGroup,
|
||||||
checkExistence,
|
|
||||||
translate as t,
|
translate as t,
|
||||||
} from '../../../../helpers/api-integration/v3';
|
} from '../../../../helpers/api-integration/v3';
|
||||||
|
|
||||||
@@ -19,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', () => {
|
context('Joining a private guild', () => {
|
||||||
let user; let invitedUser; let
|
let user;
|
||||||
guild;
|
let invitedUser;
|
||||||
|
let guild;
|
||||||
|
let invitees;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const { group, groupLeader, invitees } = await createAndPopulateGroup({
|
({ group: guild, groupLeader: user, invitees } = await createAndPopulateGroup({
|
||||||
groupDetails: {
|
groupDetails: {
|
||||||
name: 'Test Guild',
|
name: 'Test Guild',
|
||||||
type: 'guild',
|
type: 'guild',
|
||||||
privacy: 'private',
|
privacy: 'private',
|
||||||
},
|
},
|
||||||
invites: 1,
|
invites: 1,
|
||||||
});
|
upgradeToGroupPlan: true,
|
||||||
|
}));
|
||||||
|
|
||||||
guild = group;
|
[invitedUser] = invitees;
|
||||||
user = groupLeader;
|
|
||||||
invitedUser = invitees[0]; // eslint-disable-line prefer-destructuring
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns error when user is not invited to private guild', async () => {
|
it('returns error when user is not invited to private guild', async () => {
|
||||||
@@ -183,7 +125,7 @@ describe('POST /group/:groupId/join', () => {
|
|||||||
|
|
||||||
party = group;
|
party = group;
|
||||||
user = groupLeader;
|
user = groupLeader;
|
||||||
invitedUser = invitees[0]; // eslint-disable-line prefer-destructuring
|
[invitedUser] = invitees;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns error when user is not invited to party', async () => {
|
it('returns error when user is not invited to party', async () => {
|
||||||
@@ -258,47 +200,6 @@ describe('POST /group/:groupId/join', () => {
|
|||||||
await expect(user.get('/user')).to.eventually.have.nested.property('items.quests.basilist', 2);
|
await expect(user.get('/user')).to.eventually.have.nested.property('items.quests.basilist', 2);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('deletes previous party where the user was the only member', async () => {
|
|
||||||
const userToInvite = await generateUser();
|
|
||||||
const oldParty = await userToInvite.post('/groups', { // add user to a party
|
|
||||||
name: 'Another Test Party',
|
|
||||||
type: 'party',
|
|
||||||
});
|
|
||||||
|
|
||||||
await expect(checkExistence('groups', oldParty._id)).to.eventually.equal(true);
|
|
||||||
await user.post(`/groups/${party._id}/invite`, {
|
|
||||||
uuids: [userToInvite._id],
|
|
||||||
});
|
|
||||||
await userToInvite.post(`/groups/${party._id}/join`);
|
|
||||||
|
|
||||||
await expect(user.get('/user')).to.eventually.have.nested.property('party._id', party._id);
|
|
||||||
await expect(checkExistence('groups', oldParty._id)).to.eventually.equal(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('does not allow user to leave a party if a quest was active and they were the only member', async () => {
|
|
||||||
const userToInvite = await generateUser();
|
|
||||||
const oldParty = await userToInvite.post('/groups', { // add user to a party
|
|
||||||
name: 'Another Test Party',
|
|
||||||
type: 'party',
|
|
||||||
});
|
|
||||||
|
|
||||||
await userToInvite.update({
|
|
||||||
[`items.quests.${PET_QUEST}`]: 1,
|
|
||||||
});
|
|
||||||
await userToInvite.post(`/groups/${oldParty._id}/quests/invite/${PET_QUEST}`);
|
|
||||||
|
|
||||||
await expect(checkExistence('groups', oldParty._id)).to.eventually.equal(true);
|
|
||||||
await user.post(`/groups/${party._id}/invite`, {
|
|
||||||
uuids: [userToInvite._id],
|
|
||||||
});
|
|
||||||
|
|
||||||
await expect(userToInvite.post(`/groups/${party._id}/join`)).to.eventually.be.rejected.and.eql({
|
|
||||||
code: 401,
|
|
||||||
error: 'NotAuthorized',
|
|
||||||
message: t('messageCannotLeaveWhileQuesting'),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('invites joining member to active quest', async () => {
|
it('invites joining member to active quest', async () => {
|
||||||
await user.update({
|
await user.update({
|
||||||
[`items.quests.${PET_QUEST}`]: 1,
|
[`items.quests.${PET_QUEST}`]: 1,
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import {
|
|||||||
generateChallenge,
|
generateChallenge,
|
||||||
checkExistence,
|
checkExistence,
|
||||||
createAndPopulateGroup,
|
createAndPopulateGroup,
|
||||||
sleep,
|
|
||||||
generateUser,
|
generateUser,
|
||||||
translate as t,
|
translate as t,
|
||||||
} from '../../../../helpers/api-integration/v3';
|
} from '../../../../helpers/api-integration/v3';
|
||||||
@@ -14,30 +13,24 @@ import payments from '../../../../../website/server/libs/payments/payments';
|
|||||||
import calculateSubscriptionTerminationDate from '../../../../../website/server/libs/payments/calculateSubscriptionTerminationDate';
|
import calculateSubscriptionTerminationDate from '../../../../../website/server/libs/payments/calculateSubscriptionTerminationDate';
|
||||||
|
|
||||||
describe('POST /groups/:groupId/leave', () => {
|
describe('POST /groups/:groupId/leave', () => {
|
||||||
const typesOfGroups = {
|
|
||||||
'public guild': { type: 'guild', privacy: 'public' },
|
|
||||||
'private guild': { type: 'guild', privacy: 'private' },
|
|
||||||
party: { type: 'party', privacy: 'private' },
|
|
||||||
};
|
|
||||||
|
|
||||||
each(typesOfGroups, (groupDetails, groupType) => {
|
|
||||||
context(`Leaving a ${groupType}`, () => {
|
|
||||||
let groupToLeave;
|
let groupToLeave;
|
||||||
let leader;
|
let leader;
|
||||||
let member;
|
let member;
|
||||||
|
let members;
|
||||||
let memberCount;
|
let memberCount;
|
||||||
|
|
||||||
|
context('Leaving a Group Plan', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const { group, groupLeader, members } = await createAndPopulateGroup({
|
({ group: groupToLeave, groupLeader: leader, members } = await createAndPopulateGroup({
|
||||||
groupDetails,
|
type: 'guild',
|
||||||
|
privacy: 'private',
|
||||||
members: 1,
|
members: 1,
|
||||||
});
|
upgradeToGroupPlan: true,
|
||||||
|
}));
|
||||||
|
|
||||||
groupToLeave = group;
|
[member] = members;
|
||||||
leader = groupLeader;
|
memberCount = groupToLeave.memberCount;
|
||||||
member = members[0]; // eslint-disable-line prefer-destructuring
|
await leader.update({ 'auth.timestamps.created': new Date('2022-01-01') });
|
||||||
memberCount = group.memberCount;
|
|
||||||
await members[0].update({ 'auth.timestamps.created': new Date('2022-01-01') });
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('prevents non members from leaving', async () => {
|
it('prevents non members from leaving', async () => {
|
||||||
@@ -49,7 +42,7 @@ describe('POST /groups/:groupId/leave', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it(`lets user leave a ${groupType}`, async () => {
|
it('lets user leave', async () => {
|
||||||
await member.post(`/groups/${groupToLeave._id}/leave`);
|
await member.post(`/groups/${groupToLeave._id}/leave`);
|
||||||
|
|
||||||
const userThatLeftGroup = await member.get('/user');
|
const userThatLeftGroup = await member.get('/user');
|
||||||
@@ -60,7 +53,112 @@ describe('POST /groups/:groupId/leave', () => {
|
|||||||
expect(groupToLeave.memberCount).to.equal(memberCount - 1);
|
expect(groupToLeave.memberCount).to.equal(memberCount - 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it(`sets a new group leader when leader leaves a ${groupType}`, async () => {
|
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 () => {
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
context('Leaving a Party', () => {
|
||||||
|
let invitees;
|
||||||
|
let invitedUser;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
({
|
||||||
|
group: groupToLeave,
|
||||||
|
groupLeader: leader,
|
||||||
|
members,
|
||||||
|
invitees,
|
||||||
|
} = await createAndPopulateGroup({
|
||||||
|
type: 'party',
|
||||||
|
privacy: 'private',
|
||||||
|
members: 1,
|
||||||
|
invites: 1,
|
||||||
|
}));
|
||||||
|
|
||||||
|
[member] = members;
|
||||||
|
[invitedUser] = invitees;
|
||||||
|
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('sets a new group leader when leader leaves', async () => {
|
||||||
await leader.post(`/groups/${groupToLeave._id}/leave`);
|
await leader.post(`/groups/${groupToLeave._id}/leave`);
|
||||||
|
|
||||||
await groupToLeave.sync();
|
await groupToLeave.sync();
|
||||||
@@ -69,199 +167,34 @@ describe('POST /groups/:groupId/leave', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('removes new messages for that group from user', async () => {
|
it('removes new messages for that group from user', async () => {
|
||||||
await member.post(`/groups/${groupToLeave._id}/chat`, { message: 'Some message' });
|
await leader.post(`/groups/${groupToLeave._id}/chat`, { message: 'Some message' });
|
||||||
|
await member.sync();
|
||||||
|
|
||||||
await sleep(0.5);
|
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 leader.sync();
|
await member.post(`/groups/${groupToLeave._id}/leave`);
|
||||||
|
await member.sync();
|
||||||
|
|
||||||
expect(leader.notifications.find(n => n.type === 'NEW_CHAT_MESSAGE' && n.data.group.id === groupToLeave._id)).to.exist;
|
expect(member.notifications.find(n => n.type === 'NEW_CHAT_MESSAGE' && n.data.group.id === groupToLeave._id)).to.not.exist;
|
||||||
expect(leader.newMessages[groupToLeave._id]).to.not.be.empty;
|
expect(member.newMessages[groupToLeave._id]).to.be.undefined;
|
||||||
|
|
||||||
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;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
context('with challenges', () => {
|
it('removes a party when the last member leaves', async () => {
|
||||||
let challenge;
|
await member.post(`/groups/${groupToLeave._id}/leave`);
|
||||||
|
|
||||||
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`);
|
await leader.post(`/groups/${groupToLeave._id}/leave`);
|
||||||
|
|
||||||
const userWithChallengeTasks = await leader.get('/user');
|
await expect(checkExistence('party', groupToLeave._id)).to.eventually.equal(false);
|
||||||
|
|
||||||
// @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;
|
|
||||||
|
|
||||||
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,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
privateGuild = group;
|
|
||||||
leader = groupLeader;
|
|
||||||
invitedUser = invitees[0]; // eslint-disable-line prefer-destructuring
|
|
||||||
|
|
||||||
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;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
context('public guild', () => {
|
|
||||||
let publicGuild;
|
|
||||||
let leader;
|
|
||||||
let invitedUser;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
const { group, groupLeader, invitees } = await createAndPopulateGroup({
|
|
||||||
groupDetails: {
|
|
||||||
name: 'Test Public Guild',
|
|
||||||
type: 'guild',
|
|
||||||
privacy: 'public',
|
|
||||||
},
|
|
||||||
invites: 1,
|
|
||||||
});
|
|
||||||
|
|
||||||
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);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
context('party', () => {
|
|
||||||
let party;
|
|
||||||
let leader;
|
|
||||||
let invitedUser;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
const { group, groupLeader, invitees } = await createAndPopulateGroup({
|
|
||||||
groupDetails: {
|
|
||||||
name: 'Test Party',
|
|
||||||
type: 'party',
|
|
||||||
},
|
|
||||||
invites: 1,
|
|
||||||
});
|
|
||||||
|
|
||||||
party = group;
|
|
||||||
leader = groupLeader;
|
|
||||||
invitedUser = invitees[0]; // eslint-disable-line prefer-destructuring
|
|
||||||
});
|
|
||||||
|
|
||||||
it('removes a group when the last member leaves a party', async () => {
|
|
||||||
await leader.post(`/groups/${party._id}/leave`);
|
|
||||||
|
|
||||||
await expect(checkExistence('party', party._id)).to.eventually.equal(false);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('removes invitations when the last member leaves a party', async () => {
|
it('removes invitations when the last member leaves a party', async () => {
|
||||||
await leader.post(`/groups/${party._id}/leave`);
|
await member.post(`/groups/${groupToLeave._id}/leave`);
|
||||||
|
await leader.post(`/groups/${groupToLeave._id}/leave`);
|
||||||
|
|
||||||
const userWithoutInvitation = await invitedUser.get('/user');
|
const userWithoutInvitation = await invitedUser.get('/user');
|
||||||
|
|
||||||
expect(userWithoutInvitation.invitations.parties[0]).to.be.undefined;
|
expect(userWithoutInvitation.invitations.parties[0]).to.be.undefined;
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
it('deletes non existent party from user when user tries to leave', async () => {
|
it('deletes non existent party from user when user tries to leave', async () => {
|
||||||
const nonExistentPartyId = generateUUID();
|
const nonExistentPartyId = generateUUID();
|
||||||
@@ -275,23 +208,71 @@ describe('POST /groups/:groupId/leave', () => {
|
|||||||
|
|
||||||
expect(userWithNonExistentParty.party).to.eql({});
|
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) => {
|
each(typesOfGroups, (groupDetails, groupType) => {
|
||||||
context(`Leaving a group plan when the group is a ${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 groupWithPlan;
|
||||||
let leader;
|
|
||||||
let member;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const { group, groupLeader, members } = await createAndPopulateGroup({
|
({ group: groupWithPlan, groupLeader: leader, members } = await createAndPopulateGroup({
|
||||||
groupDetails,
|
groupDetails,
|
||||||
members: 1,
|
members: 1,
|
||||||
});
|
upgradeToGroupPlan: true,
|
||||||
leader = groupLeader;
|
}));
|
||||||
member = members[0]; // eslint-disable-line prefer-destructuring
|
[member] = members;
|
||||||
groupWithPlan = group;
|
|
||||||
const userWithFreePlan = await User.findById(leader._id).exec();
|
const userWithFreePlan = await User.findById(leader._id).exec();
|
||||||
|
|
||||||
// Create subscription
|
// Create subscription
|
||||||
@@ -321,45 +302,21 @@ describe('POST /groups/:groupId/leave', () => {
|
|||||||
await member.sync();
|
await member.sync();
|
||||||
expect(member.purchased.plan.dateTerminated).to.exist;
|
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) => {
|
each(typesOfGroups, (groupDetails, groupType) => {
|
||||||
context(`Leaving a group with extraMonths left plan when the group is a ${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;
|
const extraMonths = 12;
|
||||||
let groupWithPlan;
|
let groupWithPlan;
|
||||||
let member;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const { group, members } = await createAndPopulateGroup({
|
({ group: groupWithPlan, members } = await createAndPopulateGroup({
|
||||||
groupDetails,
|
groupDetails,
|
||||||
members: 1,
|
members: 1,
|
||||||
upgradeToGroupPlan: true,
|
upgradeToGroupPlan: true,
|
||||||
});
|
}));
|
||||||
[member] = members;
|
[member] = members;
|
||||||
groupWithPlan = group;
|
|
||||||
await member.update({
|
await member.update({
|
||||||
'purchased.plan.extraMonths': extraMonths,
|
'purchased.plan.extraMonths': extraMonths,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,43 +5,6 @@ import {
|
|||||||
} from '../../../../helpers/api-integration/v3';
|
} from '../../../../helpers/api-integration/v3';
|
||||||
|
|
||||||
describe('POST /group/:groupId/reject-invite', () => {
|
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', () => {
|
context('Rejecting a private guild invite', () => {
|
||||||
let invitedUser; let
|
let invitedUser; let
|
||||||
guild;
|
guild;
|
||||||
@@ -54,6 +17,7 @@ describe('POST /group/:groupId/reject-invite', () => {
|
|||||||
privacy: 'private',
|
privacy: 'private',
|
||||||
},
|
},
|
||||||
invites: 1,
|
invites: 1,
|
||||||
|
upgradeToGroupPlan: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
guild = group;
|
guild = group;
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ describe('POST /groups/:groupId/removeMember/:memberId', () => {
|
|||||||
},
|
},
|
||||||
invites: 1,
|
invites: 1,
|
||||||
members: 2,
|
members: 2,
|
||||||
|
upgradeToGroupPlan: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
guild = group;
|
guild = group;
|
||||||
@@ -129,9 +130,11 @@ describe('POST /groups/:groupId/removeMember/:memberId', () => {
|
|||||||
it('sends email to removed user', async () => {
|
it('sends email to removed user', async () => {
|
||||||
await leader.post(`/groups/${guild._id}/removeMember/${member._id}`);
|
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][0]._id).to.eql(member._id);
|
||||||
expect(email.sendTxn.args[0][1]).to.eql('kicked-from-guild');
|
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');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,25 +1,25 @@
|
|||||||
import { v4 as generateUUID } from 'uuid';
|
import { v4 as generateUUID } from 'uuid';
|
||||||
import nconf from 'nconf';
|
import nconf from 'nconf';
|
||||||
import {
|
import {
|
||||||
|
createAndPopulateGroup,
|
||||||
generateUser,
|
generateUser,
|
||||||
generateGroup,
|
|
||||||
translate as t,
|
translate as t,
|
||||||
} from '../../../../helpers/api-integration/v3';
|
} from '../../../../helpers/api-integration/v3';
|
||||||
|
|
||||||
const INVITES_LIMIT = 100;
|
const INVITES_LIMIT = 100;
|
||||||
const PARTY_LIMIT_MEMBERS = 29;
|
const PARTY_LIMIT_MEMBERS = 30;
|
||||||
const MAX_EMAIL_INVITES_BY_USER = 200;
|
const MAX_EMAIL_INVITES_BY_USER = 200;
|
||||||
|
|
||||||
describe('Post /groups/:groupId/invite', () => {
|
describe('Post /groups/:groupId/invite', () => {
|
||||||
let inviter;
|
let inviter;
|
||||||
let group;
|
let group;
|
||||||
const groupName = 'Test Public Guild';
|
const groupName = 'Test Party';
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
inviter = await generateUser({ balance: 4 });
|
inviter = await generateUser({ balance: 4 });
|
||||||
group = await inviter.post('/groups', {
|
group = await inviter.post('/groups', {
|
||||||
name: groupName,
|
name: groupName,
|
||||||
type: 'guild',
|
type: 'party',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -64,45 +64,44 @@ describe('Post /groups/:groupId/invite', () => {
|
|||||||
it('invites a user to a group by username', async () => {
|
it('invites a user to a group by username', async () => {
|
||||||
const userToInvite = await generateUser();
|
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],
|
usernames: [userToInvite.auth.local.lowerCaseUsername],
|
||||||
})).to.eventually.deep.equal([{
|
});
|
||||||
id: group._id,
|
expect(response).to.be.an('Array');
|
||||||
name: groupName,
|
expect(response[0]).to.have.all.keys(['_id', 'id', 'name', 'inviter']);
|
||||||
inviter: inviter._id,
|
expect(response[0]._id).to.be.a('String');
|
||||||
publicGuild: false,
|
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'))
|
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 () => {
|
it('invites multiple users to a group by uuid', async () => {
|
||||||
const userToInvite = await generateUser();
|
const userToInvite = await generateUser();
|
||||||
const userToInvite2 = await generateUser();
|
const userToInvite2 = await generateUser();
|
||||||
|
|
||||||
await expect(inviter.post(`/groups/${group._id}/invite`, {
|
const response = await (inviter.post(`/groups/${group._id}/invite`, {
|
||||||
usernames: [
|
usernames: [
|
||||||
userToInvite.auth.local.lowerCaseUsername,
|
userToInvite.auth.local.lowerCaseUsername,
|
||||||
userToInvite2.auth.local.lowerCaseUsername,
|
userToInvite2.auth.local.lowerCaseUsername,
|
||||||
],
|
],
|
||||||
})).to.eventually.deep.equal([
|
}));
|
||||||
{
|
expect(response).to.be.an('Array');
|
||||||
id: group._id,
|
expect(response[0]).to.have.all.keys(['_id', 'id', 'name', 'inviter']);
|
||||||
name: groupName,
|
expect(response[0]._id).to.be.a('String');
|
||||||
inviter: inviter._id,
|
expect(response[0].id).to.eql(group._id);
|
||||||
publicGuild: false,
|
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']);
|
||||||
id: group._id,
|
expect(response[1]._id).to.be.a('String');
|
||||||
name: groupName,
|
expect(response[1].id).to.eql(group._id);
|
||||||
inviter: inviter._id,
|
expect(response[1].name).to.eql(groupName);
|
||||||
publicGuild: false,
|
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(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.guilds[0].id', group._id);
|
await expect(userToInvite2.get('/user')).to.eventually.have.nested.property('invitations.parties[0].id', group._id);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -213,42 +212,42 @@ describe('Post /groups/:groupId/invite', () => {
|
|||||||
it('invites a user to a group by uuid', async () => {
|
it('invites a user to a group by uuid', async () => {
|
||||||
const userToInvite = await generateUser();
|
const userToInvite = await generateUser();
|
||||||
|
|
||||||
await expect(inviter.post(`/groups/${group._id}/invite`, {
|
const response = await inviter.post(`/groups/${group._id}/invite`, {
|
||||||
uuids: [userToInvite._id],
|
uuids: [userToInvite._id],
|
||||||
})).to.eventually.deep.equal([{
|
});
|
||||||
id: group._id,
|
expect(response).to.be.an('Array');
|
||||||
name: groupName,
|
expect(response[0]).to.have.all.keys(['_id', 'id', 'name', 'inviter']);
|
||||||
inviter: inviter._id,
|
expect(response[0]._id).to.be.a('String');
|
||||||
publicGuild: false,
|
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'))
|
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 () => {
|
it('invites multiple users to a group by uuid', async () => {
|
||||||
const userToInvite = await generateUser();
|
const userToInvite = await generateUser();
|
||||||
const userToInvite2 = 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],
|
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);
|
expect(response).to.be.an('Array');
|
||||||
await expect(userToInvite2.get('/user')).to.eventually.have.nested.property('invitations.guilds[0].id', group._id);
|
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 () => {
|
it('returns an error when inviting multiple users and a user is not found', async () => {
|
||||||
@@ -337,12 +336,8 @@ describe('Post /groups/:groupId/invite', () => {
|
|||||||
invitesSent: MAX_EMAIL_INVITES_BY_USER,
|
invitesSent: MAX_EMAIL_INVITES_BY_USER,
|
||||||
balance: 4,
|
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],
|
emails: [testInvite],
|
||||||
inviter: 'inviter name',
|
inviter: 'inviter name',
|
||||||
}))
|
}))
|
||||||
@@ -418,15 +413,15 @@ describe('Post /groups/:groupId/invite', () => {
|
|||||||
});
|
});
|
||||||
const invitedUser = await newUser.get('/user');
|
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;
|
expect(invite).to.exist;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('invites marks invite with cancelled plan', async () => {
|
it('invites user to group with cancelled plan', async () => {
|
||||||
const cancelledPlanGroup = await generateGroup(inviter, {
|
let cancelledPlanGroup;
|
||||||
type: 'guild',
|
({ group: cancelledPlanGroup, groupLeader: inviter } = await createAndPopulateGroup({
|
||||||
name: generateUUID(),
|
upgradeToGroupPlan: true,
|
||||||
});
|
}));
|
||||||
await cancelledPlanGroup.createCancelledSubscription();
|
await cancelledPlanGroup.createCancelledSubscription();
|
||||||
|
|
||||||
const newUser = await generateUser();
|
const newUser = await generateUser();
|
||||||
@@ -436,13 +431,13 @@ describe('Post /groups/:groupId/invite', () => {
|
|||||||
});
|
});
|
||||||
const invitedUser = await newUser.get('/user');
|
const invitedUser = await newUser.get('/user');
|
||||||
|
|
||||||
expect(invitedUser.invitations.guilds[0].id).to.equal(cancelledPlanGroup._id);
|
expect(invitedUser.invitations.parties[0].id).to.equal(cancelledPlanGroup._id);
|
||||||
expect(invitedUser.invitations.guilds[0].cancelledPlan).to.be.true;
|
expect(invitedUser.invitations.parties[0].cancelledPlan).to.be.true;
|
||||||
expect(invite).to.exist;
|
expect(invite).to.exist;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('guild invites', () => {
|
describe('party invites', () => {
|
||||||
it('returns an error when inviter has no chat privileges', async () => {
|
it('returns an error when inviter has no chat privileges', async () => {
|
||||||
const inviterMuted = await inviter.update({ 'flags.chatRevoked': true });
|
const inviterMuted = await inviter.update({ 'flags.chatRevoked': true });
|
||||||
const userToInvite = await generateUser();
|
const userToInvite = await generateUser();
|
||||||
@@ -456,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 () => {
|
it('returns an error when invited user has a pending invitation to the party', async () => {
|
||||||
const userToInvite = await generateUser();
|
const userToInvite = await generateUser();
|
||||||
await inviter.post(`/groups/${party._id}/invite`, {
|
await inviter.post(`/groups/${group._id}/invite`, {
|
||||||
uuids: [userToInvite._id],
|
uuids: [userToInvite._id],
|
||||||
});
|
});
|
||||||
|
|
||||||
await expect(inviter.post(`/groups/${party._id}/invite`, {
|
await expect(inviter.post(`/groups/${group._id}/invite`, {
|
||||||
uuids: [userToInvite._id],
|
uuids: [userToInvite._id],
|
||||||
}))
|
}))
|
||||||
.to.eventually.be.rejected.and.eql({
|
.to.eventually.be.rejected.and.eql({
|
||||||
@@ -565,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 () => {
|
it('returns an error when invited user is already in a party of more than 1 member', async () => {
|
||||||
const userToInvite = await generateUser();
|
const userToInvite = await generateUser();
|
||||||
const userToInvite2 = 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],
|
uuids: [userToInvite._id, userToInvite2._id],
|
||||||
});
|
});
|
||||||
await userToInvite.post(`/groups/${party._id}/join`);
|
await userToInvite.post(`/groups/${group._id}/join`);
|
||||||
await userToInvite2.post(`/groups/${party._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],
|
uuids: [userToInvite._id],
|
||||||
}))
|
}))
|
||||||
.to.eventually.be.rejected.and.eql({
|
.to.eventually.be.rejected.and.eql({
|
||||||
@@ -581,20 +486,7 @@ describe('Post /groups/:groupId/invite', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('allow inviting a user to a party if they are partying solo', async () => {
|
it('allows inviting a user to 2 different parties', async () => {
|
||||||
const userToInvite = await generateUser();
|
|
||||||
await userToInvite.post('/groups', { // add user to a party
|
|
||||||
name: 'Another Test Party',
|
|
||||||
type: 'party',
|
|
||||||
});
|
|
||||||
|
|
||||||
await inviter.post(`/groups/${party._id}/invite`, {
|
|
||||||
uuids: [userToInvite._id],
|
|
||||||
});
|
|
||||||
expect((await userToInvite.get('/user')).invitations.parties[0].id).to.equal(party._id);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('allow inviting a user to 2 different parties', async () => {
|
|
||||||
// Create another inviter
|
// Create another inviter
|
||||||
const inviter2 = await generateUser();
|
const inviter2 = await generateUser();
|
||||||
|
|
||||||
@@ -608,7 +500,7 @@ describe('Post /groups/:groupId/invite', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Invite to first party
|
// Invite to first party
|
||||||
await inviter.post(`/groups/${party._id}/invite`, {
|
await inviter.post(`/groups/${group._id}/invite`, {
|
||||||
uuids: [userToInvite._id],
|
uuids: [userToInvite._id],
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -621,49 +513,65 @@ describe('Post /groups/:groupId/invite', () => {
|
|||||||
const invitedUser = await userToInvite.get('/user');
|
const invitedUser = await userToInvite.get('/user');
|
||||||
|
|
||||||
expect(invitedUser.invitations.parties.length).to.equal(2);
|
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);
|
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({
|
const userToInvite = await generateUser({
|
||||||
party: { _id: generateUUID() },
|
party: { _id: generateUUID() },
|
||||||
});
|
});
|
||||||
|
|
||||||
await inviter.post(`/groups/${party._id}/invite`, {
|
await inviter.post(`/groups/${group._id}/invite`, {
|
||||||
uuids: [userToInvite._id],
|
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 partyLeader;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
({ group, groupLeader: partyLeader } = await createAndPopulateGroup({
|
||||||
|
groupDetails: {
|
||||||
|
name: 'Test Party',
|
||||||
|
type: 'party',
|
||||||
|
privacy: 'private',
|
||||||
|
},
|
||||||
|
// Generate party with 20 members
|
||||||
|
members: PARTY_LIMIT_MEMBERS - 10,
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('allows 30 members in a party', async () => {
|
it('allows 30 members in a party', async () => {
|
||||||
const invitesToGenerate = [];
|
const invitesToGenerate = [];
|
||||||
// Generate 29 users to invite (29 + leader = 30 members)
|
// Generate 10 new invites
|
||||||
for (let i = 0; i < PARTY_LIMIT_MEMBERS - 1; i += 1) {
|
for (let i = 1; i < 10; i += 1) {
|
||||||
invitesToGenerate.push(generateUser());
|
invitesToGenerate.push(generateUser());
|
||||||
}
|
}
|
||||||
const generatedInvites = await Promise.all(invitesToGenerate);
|
const generatedInvites = await Promise.all(invitesToGenerate);
|
||||||
// Invite users
|
// Invite users
|
||||||
expect(await inviter.post(`/groups/${party._id}/invite`, {
|
expect(await partyLeader.post(`/groups/${group._id}/invite`, {
|
||||||
uuids: generatedInvites.map(invite => invite._id),
|
uuids: generatedInvites.map(invite => invite._id),
|
||||||
})).to.be.an('array');
|
})).to.be.an('array');
|
||||||
}).timeout(10000);
|
}).timeout(10000);
|
||||||
|
|
||||||
it('does not allow 30+ members in a party', async () => {
|
it('does not allow >30 members in a party', async () => {
|
||||||
const invitesToGenerate = [];
|
const invitesToGenerate = [];
|
||||||
// Generate 30 users to invite (30 + leader = 31 members)
|
// Generate 11 invites
|
||||||
for (let i = 0; i < PARTY_LIMIT_MEMBERS; i += 1) {
|
for (let i = 1; i < 11; i += 1) {
|
||||||
invitesToGenerate.push(generateUser());
|
invitesToGenerate.push(generateUser());
|
||||||
}
|
}
|
||||||
const generatedInvites = await Promise.all(invitesToGenerate);
|
const generatedInvites = await Promise.all(invitesToGenerate);
|
||||||
// Invite users
|
// Invite users
|
||||||
await expect(inviter.post(`/groups/${party._id}/invite`, {
|
await expect(partyLeader.post(`/groups/${group._id}/invite`, {
|
||||||
uuids: generatedInvites.map(invite => invite._id),
|
uuids: generatedInvites.map(invite => invite._id),
|
||||||
}))
|
}))
|
||||||
.to.eventually.be.rejected.and.eql({
|
.to.eventually.be.rejected.and.eql({
|
||||||
code: 400,
|
code: 400,
|
||||||
error: 'BadRequest',
|
error: 'BadRequest',
|
||||||
message: t('partyExceedsMembersLimit', { maxMembersParty: PARTY_LIMIT_MEMBERS + 1 }),
|
message: t('partyExceedsMembersLimit', { maxMembersParty: PARTY_LIMIT_MEMBERS }),
|
||||||
});
|
});
|
||||||
}).timeout(10000);
|
}).timeout(10000);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -17,9 +17,10 @@ describe('POST /group/:groupId/add-manager', () => {
|
|||||||
groupDetails: {
|
groupDetails: {
|
||||||
name: groupName,
|
name: groupName,
|
||||||
type: groupType,
|
type: groupType,
|
||||||
privacy: 'public',
|
privacy: 'private',
|
||||||
},
|
},
|
||||||
members: 1,
|
members: 1,
|
||||||
|
upgradeToGroupPlan: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
groupToUpdate = group;
|
groupToUpdate = group;
|
||||||
|
|||||||
@@ -23,10 +23,11 @@ describe('PUT /group', () => {
|
|||||||
groupDetails: {
|
groupDetails: {
|
||||||
name: groupName,
|
name: groupName,
|
||||||
type: groupType,
|
type: groupType,
|
||||||
privacy: 'public',
|
privacy: 'private',
|
||||||
categories: groupCategories,
|
categories: groupCategories,
|
||||||
},
|
},
|
||||||
members: 1,
|
members: 1,
|
||||||
|
upgradeToGroupPlan: true,
|
||||||
});
|
});
|
||||||
adminUser = await generateUser({ 'permissions.moderator': true });
|
adminUser = await generateUser({ 'permissions.moderator': true });
|
||||||
groupToUpdate = group;
|
groupToUpdate = group;
|
||||||
@@ -106,14 +107,28 @@ describe('PUT /group', () => {
|
|||||||
expect(updatedGroup.name).to.equal(groupUpdatedName);
|
expect(updatedGroup.name).to.equal(groupUpdatedName);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('allows a leader to change leaders', async () => {
|
it('does not allow a leader to change leader of active group plan', async () => {
|
||||||
const updatedGroup = await leader.put(`/groups/${groupToUpdate._id}`, {
|
await expect(leader.put(`/groups/${groupToUpdate._id}`, {
|
||||||
name: groupUpdatedName,
|
name: groupUpdatedName,
|
||||||
leader: nonLeader._id,
|
leader: nonLeader._id,
|
||||||
|
})).to.eventually.be.rejected.and.eql({
|
||||||
|
code: 401,
|
||||||
|
error: 'NotAuthorized',
|
||||||
|
message: t('cannotChangeLeaderWithActiveGroupPlan'),
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(updatedGroup.leader._id).to.eql(nonLeader._id);
|
it('allows a leader of a party to change leaders', async () => {
|
||||||
expect(updatedGroup.leader.profile.name).to.eql(nonLeader.profile.name);
|
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(members[0]._id);
|
||||||
|
expect(updatedGroup.leader.profile.name).to.eql(members[0].profile.name);
|
||||||
expect(updatedGroup.name).to.equal(groupUpdatedName);
|
expect(updatedGroup.name).to.equal(groupUpdatedName);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -122,15 +137,16 @@ describe('PUT /group', () => {
|
|||||||
groupDetails: {
|
groupDetails: {
|
||||||
name: 'public guild',
|
name: 'public guild',
|
||||||
type: 'guild',
|
type: 'guild',
|
||||||
privacy: 'public',
|
privacy: 'private',
|
||||||
},
|
},
|
||||||
|
upgradeToGroupPlan: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const updateGroupDetails = {
|
const updateGroupDetails = {
|
||||||
id: group._id,
|
id: group._id,
|
||||||
name: 'public guild',
|
name: 'public guild',
|
||||||
type: 'guild',
|
type: 'guild',
|
||||||
privacy: 'public',
|
privacy: 'private',
|
||||||
bannedWordsAllowed: true,
|
bannedWordsAllowed: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -150,9 +166,11 @@ describe('PUT /group', () => {
|
|||||||
groupDetails: {
|
groupDetails: {
|
||||||
name: 'public guild',
|
name: 'public guild',
|
||||||
type: 'guild',
|
type: 'guild',
|
||||||
privacy: 'public',
|
privacy: 'private',
|
||||||
},
|
},
|
||||||
|
upgradeToGroupPlan: true,
|
||||||
});
|
});
|
||||||
|
await groupLeader.update({ permissions: {} });
|
||||||
|
|
||||||
const updateGroupDetails = {
|
const updateGroupDetails = {
|
||||||
id: group._id,
|
id: group._id,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
|
createAndPopulateGroup,
|
||||||
generateUser,
|
generateUser,
|
||||||
generateGroup,
|
|
||||||
translate as t,
|
translate as t,
|
||||||
} from '../../../../../helpers/api-integration/v3';
|
} from '../../../../../helpers/api-integration/v3';
|
||||||
import amzLib from '../../../../../../website/server/libs/payments/amazon';
|
import amzLib from '../../../../../../website/server/libs/payments/amazon';
|
||||||
@@ -50,22 +50,21 @@ describe('payments : amazon #subscribeCancel', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('cancels a group subscription', async () => {
|
it('cancels a group subscription', async () => {
|
||||||
user = await generateUser({
|
({ group, groupLeader: user } = await createAndPopulateGroup({
|
||||||
|
groupDetails: {
|
||||||
|
name: 'test group',
|
||||||
|
type: 'guild',
|
||||||
|
privacy: 'private',
|
||||||
|
},
|
||||||
|
leaderDetails: {
|
||||||
'profile.name': 'sender',
|
'profile.name': 'sender',
|
||||||
'purchased.plan.customerId': 'customer-id',
|
'purchased.plan.customerId': 'customer-id',
|
||||||
'purchased.plan.planId': 'basic_3mo',
|
'purchased.plan.planId': 'basic_3mo',
|
||||||
'purchased.plan.lastBillingDate': new Date(),
|
'purchased.plan.lastBillingDate': new Date(),
|
||||||
balance: 2,
|
balance: 2,
|
||||||
});
|
},
|
||||||
|
upgradeToGroupPlan: true,
|
||||||
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(),
|
|
||||||
});
|
|
||||||
|
|
||||||
await user.get(`${endpoint}&groupId=${group._id}`);
|
await user.get(`${endpoint}&groupId=${group._id}`);
|
||||||
|
|
||||||
|
|||||||
@@ -70,8 +70,8 @@ describe('payments - amazon - #subscribe', () => {
|
|||||||
|
|
||||||
group = await generateGroup(user, {
|
group = await generateGroup(user, {
|
||||||
name: 'test group',
|
name: 'test group',
|
||||||
type: 'guild',
|
type: 'party',
|
||||||
privacy: 'public',
|
privacy: 'private',
|
||||||
'purchased.plan.customerId': 'customer-id',
|
'purchased.plan.customerId': 'customer-id',
|
||||||
'purchased.plan.planId': 'basic_3mo',
|
'purchased.plan.planId': 'basic_3mo',
|
||||||
'purchased.plan.lastBillingDate': new Date(),
|
'purchased.plan.lastBillingDate': new Date(),
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
generateUser,
|
generateUser,
|
||||||
generateGroup,
|
|
||||||
translate as t,
|
translate as t,
|
||||||
|
createAndPopulateGroup,
|
||||||
} from '../../../../../helpers/api-integration/v3';
|
} from '../../../../../helpers/api-integration/v3';
|
||||||
import stripePayments from '../../../../../../website/server/libs/payments/stripe';
|
import stripePayments from '../../../../../../website/server/libs/payments/stripe';
|
||||||
|
|
||||||
@@ -48,22 +48,21 @@ describe('payments - stripe - #subscribeCancel', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('cancels a group subscription', async () => {
|
it('cancels a group subscription', async () => {
|
||||||
user = await generateUser({
|
({ group, groupLeader: user } = await createAndPopulateGroup({
|
||||||
|
groupDetails: {
|
||||||
|
name: 'test group',
|
||||||
|
type: 'guild',
|
||||||
|
privacy: 'private',
|
||||||
|
},
|
||||||
|
leaderDetails: {
|
||||||
'profile.name': 'sender',
|
'profile.name': 'sender',
|
||||||
'purchased.plan.customerId': 'customer-id',
|
'purchased.plan.customerId': 'customer-id',
|
||||||
'purchased.plan.planId': 'basic_3mo',
|
'purchased.plan.planId': 'basic_3mo',
|
||||||
'purchased.plan.lastBillingDate': new Date(),
|
'purchased.plan.lastBillingDate': new Date(),
|
||||||
balance: 2,
|
balance: 2,
|
||||||
});
|
},
|
||||||
|
upgradeToGroupPlan: true,
|
||||||
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(),
|
|
||||||
});
|
|
||||||
|
|
||||||
await user.get(`${endpoint}&groupId=${group._id}`);
|
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 () => {
|
it('does not accept quest for a guild', async () => {
|
||||||
const { group: guild, groupLeader: guildLeader } = await createAndPopulateGroup({
|
const { group: guild, groupLeader: guildLeader } = await createAndPopulateGroup({
|
||||||
groupDetails: { type: 'guild', privacy: 'private' },
|
groupDetails: { type: 'guild', privacy: 'private' },
|
||||||
|
upgradeToGroupPlan: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
await expect(guildLeader.post(`/groups/${guild._id}/quests/accept`))
|
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 () => {
|
it('does not force start quest for a guild', async () => {
|
||||||
const { group: guild, groupLeader: guildLeader } = await createAndPopulateGroup({
|
const { group: guild, groupLeader: guildLeader } = await createAndPopulateGroup({
|
||||||
groupDetails: { type: 'guild', privacy: 'private' },
|
groupDetails: { type: 'guild', privacy: 'private' },
|
||||||
|
upgradeToGroupPlan: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
await expect(guildLeader.post(`/groups/${guild._id}/quests/force-start`))
|
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 () => {
|
it('does not issue invites for Guilds', async () => {
|
||||||
const { group } = await createAndPopulateGroup({
|
const { group, groupLeader } = await createAndPopulateGroup({
|
||||||
groupDetails: { type: 'guild', privacy: 'public' },
|
groupDetails: { type: 'guild', privacy: 'private' },
|
||||||
members: 1,
|
members: 1,
|
||||||
|
upgradeToGroupPlan: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const alternateGroup = group;
|
await expect(groupLeader.post(`/groups/${group._id}/quests/invite/${PET_QUEST}`)).to.eventually.be.rejected.and.eql({
|
||||||
|
|
||||||
await expect(leader.post(`/groups/${alternateGroup._id}/quests/invite/${PET_QUEST}`)).to.eventually.be.rejected.and.eql({
|
|
||||||
code: 401,
|
code: 401,
|
||||||
error: 'NotAuthorized',
|
error: 'NotAuthorized',
|
||||||
message: t('guildQuestsNotSupported'),
|
message: t('guildQuestsNotSupported'),
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ describe('POST /groups/:groupId/quests/abort', () => {
|
|||||||
it('returns an error when group is a guild', async () => {
|
it('returns an error when group is a guild', async () => {
|
||||||
const { group: guild, groupLeader: guildLeader } = await createAndPopulateGroup({
|
const { group: guild, groupLeader: guildLeader } = await createAndPopulateGroup({
|
||||||
groupDetails: { type: 'guild', privacy: 'private' },
|
groupDetails: { type: 'guild', privacy: 'private' },
|
||||||
|
upgradeToGroupPlan: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
await expect(guildLeader.post(`/groups/${guild._id}/quests/abort`))
|
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 () => {
|
it('returns an error when group is a guild', async () => {
|
||||||
const { group: guild, groupLeader: guildLeader } = await createAndPopulateGroup({
|
const { group: guild, groupLeader: guildLeader } = await createAndPopulateGroup({
|
||||||
groupDetails: { type: 'guild', privacy: 'private' },
|
groupDetails: { type: 'guild', privacy: 'private' },
|
||||||
|
upgradeToGroupPlan: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
await expect(guildLeader.post(`/groups/${guild._id}/quests/cancel`))
|
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 () => {
|
it('returns an error when group is a guild', async () => {
|
||||||
const { group: guild, groupLeader: guildLeader } = await createAndPopulateGroup({
|
const { group: guild, groupLeader: guildLeader } = await createAndPopulateGroup({
|
||||||
groupDetails: { type: 'guild', privacy: 'private' },
|
groupDetails: { type: 'guild', privacy: 'private' },
|
||||||
|
upgradeToGroupPlan: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
await expect(guildLeader.post(`/groups/${guild._id}/quests/leave`))
|
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 () => {
|
it('returns an error when group is a guild', async () => {
|
||||||
const { group: guild, groupLeader: guildLeader } = await createAndPopulateGroup({
|
const { group: guild, groupLeader: guildLeader } = await createAndPopulateGroup({
|
||||||
groupDetails: { type: 'guild', privacy: 'private' },
|
groupDetails: { type: 'guild', privacy: 'private' },
|
||||||
|
upgradeToGroupPlan: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
await expect(guildLeader.post(`/groups/${guild._id}/quests/reject`))
|
await expect(guildLeader.post(`/groups/${guild._id}/quests/reject`))
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
generateUser,
|
createAndPopulateGroup,
|
||||||
generateGroup,
|
|
||||||
} from '../../../../../helpers/api-integration/v3';
|
} from '../../../../../helpers/api-integration/v3';
|
||||||
|
|
||||||
describe('POST group-tasks/:taskId/move/to/:position', () => {
|
describe('POST group-tasks/:taskId/move/to/:position', () => {
|
||||||
@@ -8,8 +7,12 @@ describe('POST group-tasks/:taskId/move/to/:position', () => {
|
|||||||
guild;
|
guild;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
user = await generateUser({ balance: 1 });
|
const { group, groupLeader } = await createAndPopulateGroup({
|
||||||
guild = await generateGroup(user, { type: 'guild' }, { 'purchased.plan.customerId': 'group-unlimited' });
|
groupDetails: { type: 'guild', privacy: 'private' },
|
||||||
|
upgradeToGroupPlan: true,
|
||||||
|
});
|
||||||
|
guild = group;
|
||||||
|
user = groupLeader;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can move task to new position', async () => {
|
it('can move task to new position', async () => {
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import {
|
import {
|
||||||
find,
|
|
||||||
each,
|
each,
|
||||||
map,
|
map,
|
||||||
} from 'lodash';
|
} from 'lodash';
|
||||||
@@ -198,95 +197,6 @@ describe('DELETE /user', () => {
|
|||||||
await expect(checkExistence('party', party._id)).to.eventually.eql(false);
|
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 () => {
|
context('user with Google auth', async () => {
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ describe('POST /user/purchase/:type/:key', () => {
|
|||||||
type: 'guild',
|
type: 'guild',
|
||||||
privacy: 'private',
|
privacy: 'private',
|
||||||
},
|
},
|
||||||
|
upgradeToGroupPlan: true,
|
||||||
});
|
});
|
||||||
await group.update({
|
await group.update({
|
||||||
'leaderOnly.getGems': true,
|
'leaderOnly.getGems': true,
|
||||||
@@ -77,6 +78,7 @@ describe('POST /user/purchase/:type/:key', () => {
|
|||||||
privacy: 'private',
|
privacy: 'private',
|
||||||
},
|
},
|
||||||
members: 1,
|
members: 1,
|
||||||
|
upgradeToGroupPlan: true,
|
||||||
});
|
});
|
||||||
await group.update({
|
await group.update({
|
||||||
'leaderOnly.getGems': true,
|
'leaderOnly.getGems': true,
|
||||||
|
|||||||
@@ -714,31 +714,6 @@ describe('POST /user/auth/local/register', () => {
|
|||||||
|
|
||||||
expect(user.invitations.party).to.eql({});
|
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', () => {
|
context('successful login via api', () => {
|
||||||
|
|||||||
@@ -202,18 +202,86 @@ describe('POST /user/class/cast/:spellId', () => {
|
|||||||
await group.groupLeader.post('/user/class/cast/mpheal');
|
await group.groupLeader.post('/user/class/cast/mpheal');
|
||||||
|
|
||||||
promises = [];
|
promises = [];
|
||||||
|
promises.push(group.groupLeader.sync());
|
||||||
promises.push(group.members[0].sync());
|
promises.push(group.members[0].sync());
|
||||||
promises.push(group.members[1].sync());
|
promises.push(group.members[1].sync());
|
||||||
promises.push(group.members[2].sync());
|
promises.push(group.members[2].sync());
|
||||||
promises.push(group.members[3].sync());
|
promises.push(group.members[3].sync());
|
||||||
await Promise.all(promises);
|
await Promise.all(promises);
|
||||||
|
|
||||||
|
expect(group.groupLeader.stats.mp).to.be.equal(170); // spell caster
|
||||||
expect(group.members[0].stats.mp).to.be.greaterThan(0); // warrior
|
expect(group.members[0].stats.mp).to.be.greaterThan(0); // warrior
|
||||||
expect(group.members[1].stats.mp).to.equal(0); // wizard
|
expect(group.members[1].stats.mp).to.equal(0); // wizard
|
||||||
expect(group.members[2].stats.mp).to.be.greaterThan(0); // rogue
|
expect(group.members[2].stats.mp).to.be.greaterThan(0); // rogue
|
||||||
expect(group.members[3].stats.mp).to.be.greaterThan(0); // healer
|
expect(group.members[3].stats.mp).to.be.greaterThan(0); // healer
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const spellList = [
|
||||||
|
{
|
||||||
|
className: 'warrior',
|
||||||
|
spells: [['smash', 'task'], ['defensiveStance'], ['valorousPresence'], ['intimidate']],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
className: 'wizard',
|
||||||
|
spells: [['fireball', 'task'], ['mpheal'], ['earth'], ['frost']],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
className: 'healer',
|
||||||
|
spells: [['heal'], ['brightness'], ['protectAura'], ['healAll']],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
className: 'rogue',
|
||||||
|
spells: [['pickPocket', 'task'], ['backStab', 'task'], ['toolsOfTrade'], ['stealth']],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
spellList.forEach(async habitClass => {
|
||||||
|
describe(`For a ${habitClass.className}`, async () => {
|
||||||
|
habitClass.spells.forEach(async spell => {
|
||||||
|
describe(`Using ${spell[0]}`, async () => {
|
||||||
|
it('Deducts MP from spell caster', async () => {
|
||||||
|
const { groupLeader } = await createAndPopulateGroup({
|
||||||
|
groupDetails: { type: 'party', privacy: 'private' },
|
||||||
|
members: 3,
|
||||||
|
});
|
||||||
|
await groupLeader.update({
|
||||||
|
'stats.mp': 200, 'stats.class': habitClass.className, 'stats.lvl': 20, 'stats.hp': 40,
|
||||||
|
});
|
||||||
|
// need this for task spells and for stealth
|
||||||
|
const task = await groupLeader.post('/tasks/user', {
|
||||||
|
text: 'test habit',
|
||||||
|
type: 'daily',
|
||||||
|
});
|
||||||
|
if (spell.length === 2 && spell[1] === 'task') {
|
||||||
|
await groupLeader.post(`/user/class/cast/${spell[0]}?targetId=${task._id}`);
|
||||||
|
} else {
|
||||||
|
await groupLeader.post(`/user/class/cast/${spell[0]}`);
|
||||||
|
}
|
||||||
|
await groupLeader.sync();
|
||||||
|
expect(groupLeader.stats.mp).to.be.lessThan(200);
|
||||||
|
});
|
||||||
|
it('works without a party', async () => {
|
||||||
|
await user.update({
|
||||||
|
'stats.mp': 200, 'stats.class': habitClass.className, 'stats.lvl': 20, 'stats.hp': 40,
|
||||||
|
});
|
||||||
|
// need this for task spells and for stealth
|
||||||
|
const task = await user.post('/tasks/user', {
|
||||||
|
text: 'test habit',
|
||||||
|
type: 'daily',
|
||||||
|
});
|
||||||
|
if (spell.length === 2 && spell[1] === 'task') {
|
||||||
|
await user.post(`/user/class/cast/${spell[0]}?targetId=${task._id}`);
|
||||||
|
} else {
|
||||||
|
await user.post(`/user/class/cast/${spell[0]}`);
|
||||||
|
}
|
||||||
|
await user.sync();
|
||||||
|
expect(user.stats.mp).to.be.lessThan(200);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('cast bulk', async () => {
|
it('cast bulk', async () => {
|
||||||
let { group, groupLeader } = await createAndPopulateGroup({ // eslint-disable-line prefer-const
|
let { group, groupLeader } = await createAndPopulateGroup({ // eslint-disable-line prefer-const
|
||||||
groupDetails: { type: 'party', privacy: 'private' },
|
groupDetails: { type: 'party', privacy: 'private' },
|
||||||
|
|||||||
@@ -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 () => {
|
it('adds a user to a guild on an invite of type other than party', async () => {
|
||||||
const { group, groupLeader } = await createAndPopulateGroup({
|
const { group, groupLeader } = await createAndPopulateGroup({
|
||||||
groupDetails: { type: 'guild', privacy: 'private' },
|
groupDetails: { type: 'guild', privacy: 'private' },
|
||||||
|
upgradeToGroupPlan: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const invite = encrypt(JSON.stringify({
|
const invite = encrypt(JSON.stringify({
|
||||||
|
|||||||
@@ -53,7 +53,8 @@ function _requestMaker (user, method, additionalSets = {}) {
|
|||||||
if (user && user._id && user.apiToken) {
|
if (user && user._id && user.apiToken) {
|
||||||
request
|
request
|
||||||
.set('x-api-user', user._id)
|
.set('x-api-user', user._id)
|
||||||
.set('x-api-key', user.apiToken);
|
.set('x-api-key', user.apiToken)
|
||||||
|
.set('x-client', 'habitica-web');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isEmpty(additionalSets)) {
|
if (!isEmpty(additionalSets)) {
|
||||||
|
|||||||
@@ -127,6 +127,9 @@ export async function createAndPopulateGroup (settings = {}) {
|
|||||||
const upgradeToGroupPlan = settings.upgradeToGroupPlan || false;
|
const upgradeToGroupPlan = settings.upgradeToGroupPlan || false;
|
||||||
const { groupDetails } = settings;
|
const { groupDetails } = settings;
|
||||||
const leaderDetails = settings.leaderDetails || { balance: 10 };
|
const leaderDetails = settings.leaderDetails || { balance: 10 };
|
||||||
|
if (upgradeToGroupPlan) {
|
||||||
|
leaderDetails.permissions = { fullAccess: true };
|
||||||
|
}
|
||||||
|
|
||||||
const groupLeader = await generateUser(leaderDetails);
|
const groupLeader = await generateUser(leaderDetails);
|
||||||
const group = await generateGroup(groupLeader, groupDetails);
|
const group = await generateGroup(groupLeader, groupDetails);
|
||||||
|
|||||||
@@ -120,6 +120,9 @@ export async function createAndPopulateGroup (settings = {}) {
|
|||||||
const upgradeToGroupPlan = settings.upgradeToGroupPlan || false;
|
const upgradeToGroupPlan = settings.upgradeToGroupPlan || false;
|
||||||
const { groupDetails } = settings;
|
const { groupDetails } = settings;
|
||||||
const leaderDetails = settings.leaderDetails || { balance: 10 };
|
const leaderDetails = settings.leaderDetails || { balance: 10 };
|
||||||
|
if (upgradeToGroupPlan) {
|
||||||
|
leaderDetails.permissions = { fullAccess: true };
|
||||||
|
}
|
||||||
|
|
||||||
const groupLeader = await generateUser(leaderDetails);
|
const groupLeader = await generateUser(leaderDetails);
|
||||||
const group = await generateGroup(groupLeader, groupDetails);
|
const group = await generateGroup(groupLeader, groupDetails);
|
||||||
|
|||||||
229
website/client/package-lock.json
generated
229
website/client/package-lock.json
generated
@@ -1842,9 +1842,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@babel/plugin-proposal-optional-chaining": {
|
"@babel/plugin-proposal-optional-chaining": {
|
||||||
"version": "7.20.7",
|
"version": "7.21.0",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.20.7.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.21.0.tgz",
|
||||||
"integrity": "sha512-T+A7b1kfjtRM51ssoOfS1+wbyCVqorfyZhT99TvxxLMirPShD8CzKMRepMlCBGM5RpHMbn8s+5MMHnPstJH6mQ==",
|
"integrity": "sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/helper-plugin-utils": "^7.20.2",
|
"@babel/helper-plugin-utils": "^7.20.2",
|
||||||
"@babel/helper-skip-transparent-expression-wrappers": "^7.20.0",
|
"@babel/helper-skip-transparent-expression-wrappers": "^7.20.0",
|
||||||
@@ -1870,9 +1870,9 @@
|
|||||||
"integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w=="
|
"integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w=="
|
||||||
},
|
},
|
||||||
"@babel/types": {
|
"@babel/types": {
|
||||||
"version": "7.20.7",
|
"version": "7.21.2",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.7.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.2.tgz",
|
||||||
"integrity": "sha512-69OnhBxSSgK0OzTJai4kyPDiKTIe3j+ctaHdIGVbRahTLAT7L3R9oeXHC2aVSuGYt3cVnoAMDmOCgJ2yaiLMvg==",
|
"integrity": "sha512-3wRZSs7jiFaB8AjxiiD+VqN5DTG2iRvJGQ+qYFrs/654lg6kGTQWIOFjlBo5RaXuAZjBmP3+OQH4dmhqiiyYxw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/helper-string-parser": "^7.19.4",
|
"@babel/helper-string-parser": "^7.19.4",
|
||||||
"@babel/helper-validator-identifier": "^7.19.1",
|
"@babel/helper-validator-identifier": "^7.19.1",
|
||||||
@@ -13318,34 +13318,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
|
"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==",
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"has-flag": {
|
|
||||||
"version": "4.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
|
||||||
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"is-fullwidth-code-point": {
|
"is-fullwidth-code-point": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
||||||
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="
|
"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==",
|
|
||||||
"optional": true,
|
|
||||||
"requires": {
|
|
||||||
"big.js": "^5.2.2",
|
|
||||||
"emojis-list": "^3.0.0",
|
|
||||||
"json5": "^2.1.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"semver": {
|
"semver": {
|
||||||
"version": "6.3.0",
|
"version": "6.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
|
||||||
@@ -13377,38 +13354,6 @@
|
|||||||
"ansi-regex": "^5.0.1"
|
"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==",
|
|
||||||
"optional": true,
|
|
||||||
"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==",
|
|
||||||
"optional": true,
|
|
||||||
"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==",
|
|
||||||
"optional": true,
|
|
||||||
"requires": {
|
|
||||||
"ansi-styles": "^4.1.0",
|
|
||||||
"supports-color": "^7.1.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"wrap-ansi": {
|
"wrap-ansi": {
|
||||||
"version": "6.2.0",
|
"version": "6.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
|
||||||
@@ -16907,9 +16852,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"core-js": {
|
"core-js": {
|
||||||
"version": "3.27.2",
|
"version": "3.31.0",
|
||||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.27.2.tgz",
|
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.31.0.tgz",
|
||||||
"integrity": "sha512-9ashVQskuh5AZEZ1JdQWp1GqSoC1e1G87MzRqg2gIfVAQ7Qn9K+uFj8EcniUFA4P2NLZfV+TOlX1SzoKfo+s7w=="
|
"integrity": "sha512-NIp2TQSGfR6ba5aalZD+ZQ1fSxGhDo/s1w0nx3RYzf2pnJxt7YynxFlFScP6eV7+GZsKO95NSjGxyJsU3DZgeQ=="
|
||||||
},
|
},
|
||||||
"core-js-compat": {
|
"core-js-compat": {
|
||||||
"version": "3.11.0",
|
"version": "3.11.0",
|
||||||
@@ -17958,9 +17903,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dompurify": {
|
"dompurify": {
|
||||||
"version": "2.4.3",
|
"version": "3.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.0.3.tgz",
|
||||||
"integrity": "sha512-q6QaLcakcRjebxjg8/+NP+h0rPfatOgOzc46Fst9VAA3jF2ApfKBNKMzdP4DYTqtUMXSCd5pRS/8Po/OmoCHZQ=="
|
"integrity": "sha512-axQ9zieHLnAnHh0sfAamKYiqXMJAVwu+LM/alQ7WDagoWessyWvMSFyW65CqF3owufNu8HBcE4cM2Vflu7YWcQ=="
|
||||||
},
|
},
|
||||||
"domutils": {
|
"domutils": {
|
||||||
"version": "1.7.0",
|
"version": "1.7.0",
|
||||||
@@ -21077,6 +21022,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz",
|
||||||
"integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg=="
|
"integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg=="
|
||||||
},
|
},
|
||||||
|
"immutable": {
|
||||||
|
"version": "4.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.0.tgz",
|
||||||
|
"integrity": "sha512-0AOCmOip+xgJwEVTQj1EfiDDOkPmuyllDuTuEX+DDXUgapLAsBIfkg3sxCYyCEA8mQqZrrxPUGjcOQ2JS3WLkg=="
|
||||||
|
},
|
||||||
"import-cwd": {
|
"import-cwd": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-2.1.0.tgz",
|
||||||
@@ -21437,9 +21387,9 @@
|
|||||||
"integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA=="
|
"integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA=="
|
||||||
},
|
},
|
||||||
"intro.js": {
|
"intro.js": {
|
||||||
"version": "6.0.0",
|
"version": "7.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/intro.js/-/intro.js-6.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/intro.js/-/intro.js-7.0.1.tgz",
|
||||||
"integrity": "sha512-ZUiR6BoLSvPSlLG0boewnWVgji1fE1gBvP/pyw5pgCKXEDQz1mMeUxarggClPNs71UTq364LwSk9zxz17A9gaQ=="
|
"integrity": "sha512-1oqz6aOz9cGQ3CrtVYhCSo6AkjnXUn302kcIWLaZ3TI4kKssRXDwDSz4VRoGcfC1jN+WfaSJXRBrITz+QVEBzg=="
|
||||||
},
|
},
|
||||||
"invariant": {
|
"invariant": {
|
||||||
"version": "2.2.4",
|
"version": "2.2.4",
|
||||||
@@ -22019,9 +21969,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"jquery": {
|
"jquery": {
|
||||||
"version": "3.6.3",
|
"version": "3.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.3.tgz",
|
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.0.tgz",
|
||||||
"integrity": "sha512-bZ5Sy3YzKo9Fyc8wH2iIQK4JImJ6R0GWI9kL1/k7Z91ZBNgkRXE6U0JfHIizZbort8ZunhSI3jw9I6253ahKfg=="
|
"integrity": "sha512-umpJ0/k8X0MvD1ds0P9SfowREz2LenHsQaxSohMZ5OMNEU2r0tf8pdeEFTHMFxWVxKNyU9rTtK3CWzUCTKJUeQ=="
|
||||||
},
|
},
|
||||||
"js-message": {
|
"js-message": {
|
||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
@@ -27367,17 +27317,19 @@
|
|||||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
||||||
},
|
},
|
||||||
"sass": {
|
"sass": {
|
||||||
"version": "1.34.0",
|
"version": "1.63.4",
|
||||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.34.0.tgz",
|
"resolved": "https://registry.npmjs.org/sass/-/sass-1.63.4.tgz",
|
||||||
"integrity": "sha512-rHEN0BscqjUYuomUEaqq3BMgsXqQfkcMVR7UhscsAVub0/spUrZGBMxQXFS2kfiDsPLZw5yuU9iJEFNC2x38Qw==",
|
"integrity": "sha512-Sx/+weUmK+oiIlI+9sdD0wZHsqpbgQg8wSwSnGBjwb5GwqFhYNwwnI+UWZtLjKvKyFlKkatRK235qQ3mokyPoQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"chokidar": ">=3.0.0 <4.0.0"
|
"chokidar": ">=3.0.0 <4.0.0",
|
||||||
|
"immutable": "^4.0.0",
|
||||||
|
"source-map-js": ">=0.6.2 <2.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"anymatch": {
|
"anymatch": {
|
||||||
"version": "3.1.2",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
|
||||||
"integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==",
|
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"normalize-path": "^3.0.0",
|
"normalize-path": "^3.0.0",
|
||||||
"picomatch": "^2.0.4"
|
"picomatch": "^2.0.4"
|
||||||
@@ -27397,18 +27349,18 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"chokidar": {
|
"chokidar": {
|
||||||
"version": "3.5.1",
|
"version": "3.5.3",
|
||||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
|
||||||
"integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==",
|
"integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"anymatch": "~3.1.1",
|
"anymatch": "~3.1.2",
|
||||||
"braces": "~3.0.2",
|
"braces": "~3.0.2",
|
||||||
"fsevents": "~2.3.1",
|
"fsevents": "~2.3.2",
|
||||||
"glob-parent": "~5.1.0",
|
"glob-parent": "~5.1.2",
|
||||||
"is-binary-path": "~2.1.0",
|
"is-binary-path": "~2.1.0",
|
||||||
"is-glob": "~4.0.1",
|
"is-glob": "~4.0.1",
|
||||||
"normalize-path": "~3.0.0",
|
"normalize-path": "~3.0.0",
|
||||||
"readdirp": "~3.5.0"
|
"readdirp": "~3.6.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"fill-range": {
|
"fill-range": {
|
||||||
@@ -27447,9 +27399,9 @@
|
|||||||
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="
|
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="
|
||||||
},
|
},
|
||||||
"readdirp": {
|
"readdirp": {
|
||||||
"version": "3.5.0",
|
"version": "3.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
|
||||||
"integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==",
|
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"picomatch": "^2.2.1"
|
"picomatch": "^2.2.1"
|
||||||
}
|
}
|
||||||
@@ -27801,9 +27753,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"smartbanner.js": {
|
"smartbanner.js": {
|
||||||
"version": "1.19.1",
|
"version": "1.19.2",
|
||||||
"resolved": "https://registry.npmjs.org/smartbanner.js/-/smartbanner.js-1.19.1.tgz",
|
"resolved": "https://registry.npmjs.org/smartbanner.js/-/smartbanner.js-1.19.2.tgz",
|
||||||
"integrity": "sha512-x3alFTlk6pLuqrm9PrYQv1E+86CrEIgPf/KJ+nP5342BmOWstbdR8OwD3TPmM56zHQm4MEr/eoqbEcfTKdvdKw=="
|
"integrity": "sha512-hwcGNp5Hza5PJHTmqP6H8q0XBYhloIQyJgdzv0ldz3HQSeEuKB2riVraQXdKuquE6ZU/0M0yubno53xJ/ZiQQg=="
|
||||||
},
|
},
|
||||||
"snapdragon": {
|
"snapdragon": {
|
||||||
"version": "0.8.2",
|
"version": "0.8.2",
|
||||||
@@ -28175,9 +28127,9 @@
|
|||||||
"integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow="
|
"integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow="
|
||||||
},
|
},
|
||||||
"stopword": {
|
"stopword": {
|
||||||
"version": "2.0.7",
|
"version": "2.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/stopword/-/stopword-2.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/stopword/-/stopword-2.0.8.tgz",
|
||||||
"integrity": "sha512-s+uLKAxrproCLrq0Wcd3JAIjlJLx6l80b2Rzt0u8+ra5SzGkHnNG8PS3DfGmYk2TrKePDVLL4SdKYwKpgSLc+w=="
|
"integrity": "sha512-btlEC2vEuhCuvshz99hSGsY8GzaP5qzDPQm56j6rR/R38p8xdsOXgU5a6tIgvU/4hcCta1Vlo/2FVXA9m0f8XA=="
|
||||||
},
|
},
|
||||||
"store2": {
|
"store2": {
|
||||||
"version": "2.10.0",
|
"version": "2.10.0",
|
||||||
@@ -30271,9 +30223,9 @@
|
|||||||
"integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
|
"integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
|
||||||
},
|
},
|
||||||
"uuid": {
|
"uuid": {
|
||||||
"version": "8.3.2",
|
"version": "9.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz",
|
||||||
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
|
"integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg=="
|
||||||
},
|
},
|
||||||
"uuid-browser": {
|
"uuid-browser": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
@@ -30629,6 +30581,85 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"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==",
|
||||||
|
"optional": true,
|
||||||
|
"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==",
|
||||||
|
"optional": true,
|
||||||
|
"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==",
|
||||||
|
"optional": true,
|
||||||
|
"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==",
|
||||||
|
"optional": true,
|
||||||
|
"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==",
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"emojis-list": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==",
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"has-flag": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"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==",
|
||||||
|
"optional": true,
|
||||||
|
"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==",
|
||||||
|
"optional": true,
|
||||||
|
"requires": {
|
||||||
|
"has-flag": "^4.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"vue-mugen-scroll": {
|
"vue-mugen-scroll": {
|
||||||
"version": "0.2.6",
|
"version": "0.2.6",
|
||||||
"resolved": "https://registry.npmjs.org/vue-mugen-scroll/-/vue-mugen-scroll-0.2.6.tgz",
|
"resolved": "https://registry.npmjs.org/vue-mugen-scroll/-/vue-mugen-scroll-0.2.6.tgz",
|
||||||
|
|||||||
@@ -32,8 +32,8 @@
|
|||||||
"bootstrap": "^4.6.0",
|
"bootstrap": "^4.6.0",
|
||||||
"bootstrap-vue": "^2.23.1",
|
"bootstrap-vue": "^2.23.1",
|
||||||
"chai": "^4.3.7",
|
"chai": "^4.3.7",
|
||||||
"core-js": "^3.27.2",
|
"core-js": "^3.31.0",
|
||||||
"dompurify": "^2.4.3",
|
"dompurify": "^3.0.3",
|
||||||
"eslint": "^6.8.0",
|
"eslint": "^6.8.0",
|
||||||
"eslint-config-habitrpg": "^6.2.0",
|
"eslint-config-habitrpg": "^6.2.0",
|
||||||
"eslint-plugin-mocha": "^5.3.0",
|
"eslint-plugin-mocha": "^5.3.0",
|
||||||
@@ -41,20 +41,20 @@
|
|||||||
"habitica-markdown": "^3.0.0",
|
"habitica-markdown": "^3.0.0",
|
||||||
"hellojs": "^1.20.0",
|
"hellojs": "^1.20.0",
|
||||||
"inspectpack": "^4.7.1",
|
"inspectpack": "^4.7.1",
|
||||||
"intro.js": "^6.0.0",
|
"intro.js": "^7.0.1",
|
||||||
"jquery": "^3.6.3",
|
"jquery": "^3.7.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"moment": "^2.29.4",
|
"moment": "^2.29.4",
|
||||||
"nconf": "^0.12.0",
|
"nconf": "^0.12.0",
|
||||||
"sass": "^1.34.0",
|
"sass": "^1.63.4",
|
||||||
"sass-loader": "^8.0.2",
|
"sass-loader": "^8.0.2",
|
||||||
"smartbanner.js": "^1.19.1",
|
"smartbanner.js": "^1.19.2",
|
||||||
"stopword": "^2.0.7",
|
"stopword": "^2.0.8",
|
||||||
"svg-inline-loader": "^0.8.2",
|
"svg-inline-loader": "^0.8.2",
|
||||||
"svg-url-loader": "^7.1.1",
|
"svg-url-loader": "^7.1.1",
|
||||||
"svgo": "^1.3.2",
|
"svgo": "^1.3.2",
|
||||||
"svgo-loader": "^2.2.1",
|
"svgo-loader": "^2.2.1",
|
||||||
"uuid": "^8.3.2",
|
"uuid": "^9.0.0",
|
||||||
"validator": "^13.9.0",
|
"validator": "^13.9.0",
|
||||||
"vue": "^2.7.10",
|
"vue": "^2.7.10",
|
||||||
"vue-cli-plugin-storybook": "2.1.0",
|
"vue-cli-plugin-storybook": "2.1.0",
|
||||||
@@ -66,6 +66,6 @@
|
|||||||
"webpack": "^4.46.0"
|
"webpack": "^4.46.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/plugin-proposal-optional-chaining": "^7.20.7"
|
"@babel/plugin-proposal-optional-chaining": "^7.21.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
website/client/public/static/npc/normal/pixel_border.png
Normal file
BIN
website/client/public/static/npc/normal/pixel_border.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 148 B |
@@ -35,11 +35,13 @@
|
|||||||
<sub-canceled-modal v-if="isUserLoaded" />
|
<sub-canceled-modal v-if="isUserLoaded" />
|
||||||
<bug-report-modal v-if="isUserLoaded" />
|
<bug-report-modal v-if="isUserLoaded" />
|
||||||
<bug-report-success-modal v-if="isUserLoaded" />
|
<bug-report-success-modal v-if="isUserLoaded" />
|
||||||
|
<external-link-modal />
|
||||||
<birthday-modal />
|
<birthday-modal />
|
||||||
<snackbars />
|
<snackbars />
|
||||||
<router-view v-if="!isUserLoggedIn || isStaticPage" />
|
<router-view v-if="!isUserLoggedIn || isStaticPage" />
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<template v-if="isUserLoaded">
|
<template v-if="isUserLoaded">
|
||||||
|
<chat-banner />
|
||||||
<damage-paused-banner />
|
<damage-paused-banner />
|
||||||
<gems-promo-banner />
|
<gems-promo-banner />
|
||||||
<gift-promo-banner />
|
<gift-promo-banner />
|
||||||
@@ -158,6 +160,7 @@ import { loadProgressBar } from 'axios-progress-bar';
|
|||||||
import birthdayModal from '@/components/news/birthdayModal';
|
import birthdayModal from '@/components/news/birthdayModal';
|
||||||
import AppMenu from './components/header/menu';
|
import AppMenu from './components/header/menu';
|
||||||
import AppHeader from './components/header/index';
|
import AppHeader from './components/header/index';
|
||||||
|
import ChatBanner from './components/header/banners/chatBanner';
|
||||||
import DamagePausedBanner from './components/header/banners/damagePaused';
|
import DamagePausedBanner from './components/header/banners/damagePaused';
|
||||||
import GemsPromoBanner from './components/header/banners/gemsPromo';
|
import GemsPromoBanner from './components/header/banners/gemsPromo';
|
||||||
import GiftPromoBanner from './components/header/banners/giftPromo';
|
import GiftPromoBanner from './components/header/banners/giftPromo';
|
||||||
@@ -175,6 +178,7 @@ import amazonPaymentsModal from '@/components/payments/amazonModal';
|
|||||||
import paymentsSuccessModal from '@/components/payments/successModal';
|
import paymentsSuccessModal from '@/components/payments/successModal';
|
||||||
import subCancelModalConfirm from '@/components/payments/cancelModalConfirm';
|
import subCancelModalConfirm from '@/components/payments/cancelModalConfirm';
|
||||||
import subCanceledModal from '@/components/payments/canceledModal';
|
import subCanceledModal from '@/components/payments/canceledModal';
|
||||||
|
import externalLinkModal from '@/components/externalLinkModal.vue';
|
||||||
|
|
||||||
import spellsMixin from '@/mixins/spells';
|
import spellsMixin from '@/mixins/spells';
|
||||||
import {
|
import {
|
||||||
@@ -196,6 +200,7 @@ export default {
|
|||||||
AppHeader,
|
AppHeader,
|
||||||
AppFooter,
|
AppFooter,
|
||||||
birthdayModal,
|
birthdayModal,
|
||||||
|
ChatBanner,
|
||||||
DamagePausedBanner,
|
DamagePausedBanner,
|
||||||
GemsPromoBanner,
|
GemsPromoBanner,
|
||||||
GiftPromoBanner,
|
GiftPromoBanner,
|
||||||
@@ -210,6 +215,7 @@ export default {
|
|||||||
subCanceledModal,
|
subCanceledModal,
|
||||||
bugReportModal,
|
bugReportModal,
|
||||||
bugReportSuccessModal,
|
bugReportSuccessModal,
|
||||||
|
externalLinkModal,
|
||||||
},
|
},
|
||||||
mixins: [notifications, spellsMixin],
|
mixins: [notifications, spellsMixin],
|
||||||
data () {
|
data () {
|
||||||
|
|||||||
@@ -94,6 +94,12 @@
|
|||||||
height: 90px;
|
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 {
|
.head_special_0 {
|
||||||
background: url("https://habitica-assets.s3.amazonaws.com/mobileApp/images/BackerOnly-Equip-ShadeHelmet.gif") no-repeat;
|
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;
|
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_Head_"],
|
||||||
[class*="Mount_Body_"] {
|
[class*="Mount_Body_"] {
|
||||||
margin-top:18px; /* Sprite accommodates 105x123 box */
|
margin-top:18px; /* Sprite accommodates 105x123 box */
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -24,9 +24,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-16 {
|
.icon-10 {
|
||||||
width: 16px;
|
width: 10px;
|
||||||
height: 16px;
|
height: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-12 {
|
.icon-12 {
|
||||||
@@ -34,21 +34,26 @@
|
|||||||
height: 12px;
|
height: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.icon-16 {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
.icon-24 {
|
.icon-24 {
|
||||||
width: 24px;
|
width: 24px;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.icon-32 {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
.icon-48 {
|
.icon-48 {
|
||||||
width: 48px;
|
width: 48px;
|
||||||
height: 48px;
|
height: 48px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-10 {
|
|
||||||
width: 10px;
|
|
||||||
height: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.inline {
|
.inline {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|||||||
1
website/client/src/assets/svg/sync-2.svg
Normal file
1
website/client/src/assets/svg/sync-2.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?><svg id="a" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path id="b" d="m10,6h6V0h-2v2.72C12.49.99,10.3,0,8,0,3.59,0,0,3.59,0,8s3.59,8,8,8c2.69,0,5.2-1.35,6.68-3.6l-1.67-1.1c-1.11,1.69-2.99,2.71-5.01,2.7-3.31,0-6-2.69-6-6s2.69-6,6-6c1.72,0,3.33.74,4.46,2h-2.46v2Z" fill-rule="evenodd"/></svg>
|
||||||
|
After Width: | Height: | Size: 341 B |
@@ -1,18 +1,26 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="row">
|
<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?-->
|
<!-- @TODO i18n. How to setup the strings with the router-link inside?-->
|
||||||
<img
|
<img
|
||||||
class="not-found-img"
|
:class="retiredChatPage ? 'mt-5' : 'image-404'"
|
||||||
src="~@/assets/images/404.png"
|
src="~@/assets/images/404.png"
|
||||||
>
|
>
|
||||||
<h1 class="not-found">
|
<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.
|
Sometimes even the bravest adventurer gets lost.
|
||||||
</h1>
|
</h1>
|
||||||
<h2 class="not-found">
|
<p class="mb-0">
|
||||||
Looks like this link is broken or the page may have moved, sorry!
|
Looks like this link is broken or the page may have moved, sorry!
|
||||||
</h2>
|
</p>
|
||||||
<h2 class="not-found">
|
<p>
|
||||||
Head back to the
|
Head back to the
|
||||||
<router-link to="/">
|
<router-link to="/">
|
||||||
Homepage
|
Homepage
|
||||||
@@ -20,7 +28,8 @@
|
|||||||
<router-link :to="contactUsLink">
|
<router-link :to="contactUsLink">
|
||||||
Contact Us
|
Contact Us
|
||||||
</router-link>about the issue.
|
</router-link>about the issue.
|
||||||
</h2>
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -37,6 +46,9 @@ export default {
|
|||||||
}
|
}
|
||||||
return { name: 'contact' };
|
return { name: 'contact' };
|
||||||
},
|
},
|
||||||
|
retiredChatPage () {
|
||||||
|
return this.$route.fullPath.indexOf('/groups') !== -1;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
@@ -44,28 +56,20 @@ export default {
|
|||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import '~@/assets/scss/colors.scss';
|
@import '~@/assets/scss/colors.scss';
|
||||||
|
|
||||||
.col-12 {
|
h1, .static-wrapper h1 {
|
||||||
margin-bottom: 120px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.not-found-img {
|
|
||||||
margin-top: 152px;
|
|
||||||
margin-bottom: 42px;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1.not-found {
|
|
||||||
line-height: 1.33;
|
|
||||||
color: $purple-200;
|
color: $purple-200;
|
||||||
margin-bottom: 8px;
|
line-height: 1.33;
|
||||||
font-weight: normal;
|
margin-top: 3rem;
|
||||||
margin-top: 0px;
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
h2.not-found {
|
p {
|
||||||
line-height: 1.4;
|
font-size: 16px;
|
||||||
font-weight: normal;
|
line-height: 1.75;
|
||||||
color: $gray-200;
|
|
||||||
margin-bottom: 0px;
|
|
||||||
margin-top: 0px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.image-404 {
|
||||||
|
margin-top: 104px;
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</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>
|
|
||||||
@@ -22,6 +22,10 @@
|
|||||||
Account created:
|
Account created:
|
||||||
<strong>{{ hero.auth.timestamps.created | formatDate }}</strong>
|
<strong>{{ hero.auth.timestamps.created | formatDate }}</strong>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="hero.flags.thirdPartyTools">
|
||||||
|
User has employed <strong>third party tools</strong>. Last known usage:
|
||||||
|
<strong>{{ hero.flags.thirdPartyTools | formatDate }}</strong>
|
||||||
|
</div>
|
||||||
<div v-if="cronError">
|
<div v-if="cronError">
|
||||||
"lastCron" value:
|
"lastCron" value:
|
||||||
<strong>{{ hero.lastCron | formatDate }}</strong>
|
<strong>{{ hero.lastCron | formatDate }}</strong>
|
||||||
|
|||||||
@@ -17,10 +17,18 @@
|
|||||||
Payment schedule ("basic-earned" is monthly):
|
Payment schedule ("basic-earned" is monthly):
|
||||||
<strong>{{ hero.purchased.plan.planId }}</strong>
|
<strong>{{ hero.purchased.plan.planId }}</strong>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="hero.purchased.plan.planId == 'group_plan_auto'">
|
||||||
|
Group plan ID:
|
||||||
|
<strong>{{ hero.purchased.plan.owner }}</strong>
|
||||||
|
</div>
|
||||||
<div v-if="hero.purchased.plan.dateCreated">
|
<div v-if="hero.purchased.plan.dateCreated">
|
||||||
Creation date:
|
Creation date:
|
||||||
<strong>{{ dateFormat(hero.purchased.plan.dateCreated) }}</strong>
|
<strong>{{ dateFormat(hero.purchased.plan.dateCreated) }}</strong>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="hero.purchased.plan.dateCurrentTypeCreated">
|
||||||
|
Start date for current subscription type:
|
||||||
|
<strong>{{ dateFormat(hero.purchased.plan.dateCurrentTypeCreated) }}</strong>
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
Termination date:
|
Termination date:
|
||||||
<strong
|
<strong
|
||||||
@@ -46,9 +54,16 @@
|
|||||||
Perk offset months:
|
Perk offset months:
|
||||||
<strong>{{ hero.purchased.plan.consecutive.offset }}</strong>
|
<strong>{{ hero.purchased.plan.consecutive.offset }}</strong>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div class="form-inline">
|
||||||
Perk month count:
|
Perk month count:
|
||||||
<strong>{{ hero.purchased.plan.perkMonthCount }}</strong>
|
<input
|
||||||
|
v-model="hero.purchased.plan.perkMonthCount"
|
||||||
|
class="form-control"
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
max="2"
|
||||||
|
step="1"
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
Next Mystic Hourglass:
|
Next Mystic Hourglass:
|
||||||
|
|||||||
@@ -213,7 +213,7 @@
|
|||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
class="social-circle"
|
class="social-circle"
|
||||||
href="https://twitter.com/habitica"
|
href="https://twitter.com/habitica/"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
@@ -223,7 +223,7 @@
|
|||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
class="social-circle"
|
class="social-circle"
|
||||||
href="https://www.facebook.com/Habitica"
|
href="https://www.facebook.com/Habitica/"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -8,24 +8,16 @@
|
|||||||
slot="modal-header"
|
slot="modal-header"
|
||||||
class="bug-report-modal-header"
|
class="bug-report-modal-header"
|
||||||
>
|
>
|
||||||
<h2 v-once>
|
<h2>
|
||||||
{{ $t('reportBug') }}
|
{{ question ? $t('askQuestion') : $t('reportBug') }}
|
||||||
</h2>
|
</h2>
|
||||||
|
<div class="report-bug-header-describe">
|
||||||
<div
|
{{ question ? $t('askQuestionHeaderDescribe') : $t('reportBugHeaderDescribe') }}
|
||||||
v-once
|
|
||||||
class="report-bug-header-describe"
|
|
||||||
>
|
|
||||||
{{ $t('reportBugHeaderDescribe') }}
|
|
||||||
</div>
|
</div>
|
||||||
|
<close-x
|
||||||
<div class="dialog-close">
|
@close="close()"
|
||||||
<close-icon
|
|
||||||
:purple="true"
|
|
||||||
@click="close()"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div>
|
<div>
|
||||||
<form
|
<form
|
||||||
class="form"
|
class="form"
|
||||||
@@ -40,11 +32,8 @@
|
|||||||
>
|
>
|
||||||
{{ $t('email') }}
|
{{ $t('email') }}
|
||||||
</label>
|
</label>
|
||||||
<div
|
<div class="mb-2 description-label">
|
||||||
v-once
|
{{ question ? $t('questionEmailText') : $t('reportEmailText') }}
|
||||||
class="mb-2 description-label"
|
|
||||||
>
|
|
||||||
{{ $t('reportEmailText') }}
|
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
id="emailInput"
|
id="emailInput"
|
||||||
@@ -64,21 +53,18 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<label v-once>
|
<label>
|
||||||
{{ $t('reportDescription') }}
|
{{ question ? $t('question') : $t('reportDescription') }}
|
||||||
</label>
|
</label>
|
||||||
<div
|
<div class="mb-2 description-label">
|
||||||
v-once
|
{{ question ? $t('questionDescriptionText') : $t('reportDescriptionText') }}
|
||||||
class="mb-2 description-label"
|
|
||||||
>
|
|
||||||
{{ $t('reportDescriptionText') }}
|
|
||||||
</div>
|
</div>
|
||||||
<textarea
|
<textarea
|
||||||
v-model="message"
|
v-model="message"
|
||||||
class="form-control"
|
class="form-control"
|
||||||
rows="5"
|
rows="5"
|
||||||
:required="true"
|
:required="true"
|
||||||
:placeholder="$t('reportDescriptionPlaceholder')"
|
:placeholder="question ? $t('questionPlaceholder') : $t('reportDescriptionPlaceholder')"
|
||||||
:class="{'input-invalid': messageInvalid && this.message.length === 0}"
|
:class="{'input-invalid': messageInvalid && this.message.length === 0}"
|
||||||
>
|
>
|
||||||
|
|
||||||
@@ -89,7 +75,7 @@
|
|||||||
type="submit"
|
type="submit"
|
||||||
:disabled="!message || !emailValid"
|
:disabled="!message || !emailValid"
|
||||||
>
|
>
|
||||||
{{ $t('submitBugReport') }}
|
{{ question ? $t('submitQuestion') : $t('submitBugReport') }}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@@ -141,7 +127,7 @@ h2 {
|
|||||||
.bug-report-modal-header {
|
.bug-report-modal-header {
|
||||||
color: $white;
|
color: $white;
|
||||||
width: 100%;
|
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});
|
background-image: linear-gradient(288deg, #{$purple-200}, #{$purple-300});
|
||||||
}
|
}
|
||||||
@@ -182,13 +168,13 @@ label {
|
|||||||
<script>
|
<script>
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import isEmail from 'validator/lib/isEmail';
|
import isEmail from 'validator/lib/isEmail';
|
||||||
import closeIcon from '@/components/shared/closeIcon';
|
import closeX from '@/components/ui/closeX';
|
||||||
import { mapState } from '@/libs/store';
|
import { mapState } from '@/libs/store';
|
||||||
import { MODALS } from '@/libs/consts';
|
import { MODALS } from '@/libs/consts';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
closeIcon,
|
closeX,
|
||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
@@ -211,6 +197,7 @@ export default {
|
|||||||
await axios.post('/api/v4/bug-report', {
|
await axios.post('/api/v4/bug-report', {
|
||||||
message: this.message,
|
message: this.message,
|
||||||
email: this.email,
|
email: this.email,
|
||||||
|
question: this.question,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.message = '';
|
this.message = '';
|
||||||
@@ -233,6 +220,9 @@ export default {
|
|||||||
if (this.email.length <= 3) return false;
|
if (this.email.length <= 3) return false;
|
||||||
return !this.emailValid;
|
return !this.emailValid;
|
||||||
},
|
},
|
||||||
|
question () {
|
||||||
|
return this.$store.state.bugReportOptions.question;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
mounted () {
|
mounted () {
|
||||||
const { user } = this;
|
const { user } = this;
|
||||||
|
|||||||
@@ -322,6 +322,7 @@ import omit from 'lodash/omit';
|
|||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
|
|
||||||
import { userStateMixin } from '../../mixins/userState';
|
import { userStateMixin } from '../../mixins/userState';
|
||||||
|
import externalLinks from '../../mixins/externalLinks';
|
||||||
import memberSearchDropdown from '@/components/members/memberSearchDropdown';
|
import memberSearchDropdown from '@/components/members/memberSearchDropdown';
|
||||||
import closeChallengeModal from './closeChallengeModal';
|
import closeChallengeModal from './closeChallengeModal';
|
||||||
import Column from '../tasks/column';
|
import Column from '../tasks/column';
|
||||||
@@ -358,7 +359,7 @@ export default {
|
|||||||
userLink,
|
userLink,
|
||||||
groupLink,
|
groupLink,
|
||||||
},
|
},
|
||||||
mixins: [challengeMemberSearchMixin, userStateMixin],
|
mixins: [challengeMemberSearchMixin, externalLinks, userStateMixin],
|
||||||
props: ['challengeId'],
|
props: ['challengeId'],
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
@@ -414,6 +415,10 @@ export default {
|
|||||||
mounted () {
|
mounted () {
|
||||||
if (!this.searchId) this.searchId = this.challengeId;
|
if (!this.searchId) this.searchId = this.challengeId;
|
||||||
if (!this.challenge._id) this.loadChallenge();
|
if (!this.challenge._id) this.loadChallenge();
|
||||||
|
this.handleExternalLinks();
|
||||||
|
},
|
||||||
|
updated () {
|
||||||
|
this.handleExternalLinks();
|
||||||
},
|
},
|
||||||
async beforeRouteUpdate (to, from, next) {
|
async beforeRouteUpdate (to, from, next) {
|
||||||
this.searchId = to.params.challengeId;
|
this.searchId = to.params.challengeId;
|
||||||
|
|||||||
@@ -120,6 +120,7 @@ import { mapState } from '@/libs/store';
|
|||||||
import Sidebar from './sidebar';
|
import Sidebar from './sidebar';
|
||||||
import ChallengeItem from './challengeItem';
|
import ChallengeItem from './challengeItem';
|
||||||
import challengeModal from './challengeModal';
|
import challengeModal from './challengeModal';
|
||||||
|
import externalLinks from '@/mixins/externalLinks';
|
||||||
import challengeUtilities from '@/mixins/challengeUtilities';
|
import challengeUtilities from '@/mixins/challengeUtilities';
|
||||||
|
|
||||||
import positiveIcon from '@/assets/svg/positive.svg';
|
import positiveIcon from '@/assets/svg/positive.svg';
|
||||||
@@ -131,7 +132,7 @@ export default {
|
|||||||
challengeModal,
|
challengeModal,
|
||||||
MugenScroll,
|
MugenScroll,
|
||||||
},
|
},
|
||||||
mixins: [challengeUtilities],
|
mixins: [challengeUtilities, externalLinks],
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
loading: true,
|
loading: true,
|
||||||
@@ -177,6 +178,10 @@ export default {
|
|||||||
section: this.$t('challenges'),
|
section: this.$t('challenges'),
|
||||||
});
|
});
|
||||||
this.loadChallenges();
|
this.loadChallenges();
|
||||||
|
this.handleExternalLinks();
|
||||||
|
},
|
||||||
|
updated () {
|
||||||
|
this.handleExternalLinks();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
updateSearch (eventData) {
|
updateSearch (eventData) {
|
||||||
|
|||||||
@@ -81,6 +81,8 @@ import challengeModal from './challengeModal';
|
|||||||
import { mapState } from '@/libs/store';
|
import { mapState } from '@/libs/store';
|
||||||
import markdownDirective from '@/directives/markdown';
|
import markdownDirective from '@/directives/markdown';
|
||||||
|
|
||||||
|
import externalLinks from '../../mixins/externalLinks';
|
||||||
|
|
||||||
import challengeItem from './challengeItem';
|
import challengeItem from './challengeItem';
|
||||||
import challengeIcon from '@/assets/svg/challenge.svg';
|
import challengeIcon from '@/assets/svg/challenge.svg';
|
||||||
|
|
||||||
@@ -92,6 +94,7 @@ export default {
|
|||||||
directives: {
|
directives: {
|
||||||
markdown: markdownDirective,
|
markdown: markdownDirective,
|
||||||
},
|
},
|
||||||
|
mixins: [externalLinks],
|
||||||
props: ['group'],
|
props: ['group'],
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
@@ -118,6 +121,10 @@ export default {
|
|||||||
},
|
},
|
||||||
mounted () {
|
mounted () {
|
||||||
this.loadChallenges();
|
this.loadChallenges();
|
||||||
|
this.handleExternalLinks();
|
||||||
|
},
|
||||||
|
updated () {
|
||||||
|
this.handleExternalLinks();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async loadChallenges () {
|
async loadChallenges () {
|
||||||
|
|||||||
@@ -145,6 +145,7 @@ import Sidebar from './sidebar';
|
|||||||
import ChallengeItem from './challengeItem';
|
import ChallengeItem from './challengeItem';
|
||||||
import challengeModal from './challengeModal';
|
import challengeModal from './challengeModal';
|
||||||
import challengeUtilities from '@/mixins/challengeUtilities';
|
import challengeUtilities from '@/mixins/challengeUtilities';
|
||||||
|
import externalLinks from '@/mixins/externalLinks';
|
||||||
|
|
||||||
import challengeIcon from '@/assets/svg/challenge.svg';
|
import challengeIcon from '@/assets/svg/challenge.svg';
|
||||||
import positiveIcon from '@/assets/svg/positive.svg';
|
import positiveIcon from '@/assets/svg/positive.svg';
|
||||||
@@ -156,7 +157,7 @@ export default {
|
|||||||
challengeModal,
|
challengeModal,
|
||||||
MugenScroll,
|
MugenScroll,
|
||||||
},
|
},
|
||||||
mixins: [challengeUtilities],
|
mixins: [challengeUtilities, externalLinks],
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
icons: Object.freeze({
|
icons: Object.freeze({
|
||||||
@@ -203,6 +204,10 @@ export default {
|
|||||||
section: this.$t('challenges'),
|
section: this.$t('challenges'),
|
||||||
});
|
});
|
||||||
this.loadChallenges();
|
this.loadChallenges();
|
||||||
|
this.handleExternalLinks();
|
||||||
|
},
|
||||||
|
updated () {
|
||||||
|
this.handleExternalLinks();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
updateSearch (eventData) {
|
updateSearch (eventData) {
|
||||||
|
|||||||
@@ -50,7 +50,6 @@ import notificationsMixin from '@/mixins/notifications';
|
|||||||
import Task from '@/components/tasks/task';
|
import Task from '@/components/tasks/task';
|
||||||
|
|
||||||
import taskDefaults from '@/../../common/script/libs/taskDefaults';
|
import taskDefaults from '@/../../common/script/libs/taskDefaults';
|
||||||
import { TAVERN_ID } from '@/../../common/script/constants';
|
|
||||||
|
|
||||||
const baseUrl = 'https://habitica.com';
|
const baseUrl = 'https://habitica.com';
|
||||||
|
|
||||||
@@ -89,9 +88,7 @@ export default {
|
|||||||
createTask: 'tasks:create',
|
createTask: 'tasks:create',
|
||||||
}),
|
}),
|
||||||
groupPath () {
|
groupPath () {
|
||||||
if (this.groupId === TAVERN_ID) {
|
if (this.groupType === 'party') {
|
||||||
return `${baseUrl}/groups/tavern`;
|
|
||||||
} if (this.groupType === 'party') {
|
|
||||||
return `${baseUrl}/party`;
|
return `${baseUrl}/party`;
|
||||||
}
|
}
|
||||||
return `${baseUrl}/groups/guild/${this.groupId}`;
|
return `${baseUrl}/groups/guild/${this.groupId}`;
|
||||||
|
|||||||
@@ -183,10 +183,8 @@
|
|||||||
<div
|
<div
|
||||||
v-for="bg in backgroundShopSets[0].items"
|
v-for="bg in backgroundShopSets[0].items"
|
||||||
:key="bg.key"
|
:key="bg.key"
|
||||||
|
:id="bg.key"
|
||||||
class="col-2"
|
class="col-2"
|
||||||
:popover-title="bg.text"
|
|
||||||
:popover="bg.notes"
|
|
||||||
popover-trigger="mouseenter"
|
|
||||||
@click="unlock('background.' + bg.key)"
|
@click="unlock('background.' + bg.key)"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
@@ -195,6 +193,13 @@
|
|||||||
>
|
>
|
||||||
<div class="small-rectangle"></div>
|
<div class="small-rectangle"></div>
|
||||||
</div>
|
</div>
|
||||||
|
<b-popover
|
||||||
|
:target="bg.key"
|
||||||
|
triggers="hover focus"
|
||||||
|
placement="bottom"
|
||||||
|
:prevent-overflow="false"
|
||||||
|
:content="bg.notes"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@@ -211,16 +216,21 @@
|
|||||||
<div
|
<div
|
||||||
v-for="bg in backgroundShopSets[2].items"
|
v-for="bg in backgroundShopSets[2].items"
|
||||||
:key="bg.key"
|
:key="bg.key"
|
||||||
|
:id="bg.key"
|
||||||
class="col-4 text-center customize-option background-button"
|
class="col-4 text-center customize-option background-button"
|
||||||
:popover-title="bg.text"
|
|
||||||
:popover="bg.notes"
|
|
||||||
popover-trigger="mouseenter"
|
|
||||||
@click="unlock('background.' + bg.key)"
|
@click="unlock('background.' + bg.key)"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="background"
|
class="background"
|
||||||
:class="`background_${bg.key}`"
|
:class="`background_${bg.key}`"
|
||||||
></div>
|
></div>
|
||||||
|
<b-popover
|
||||||
|
:target="bg.key"
|
||||||
|
triggers="hover focus"
|
||||||
|
placement="bottom"
|
||||||
|
:prevent-overflow="false"
|
||||||
|
:content="bg.notes"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -236,10 +246,8 @@
|
|||||||
<div
|
<div
|
||||||
v-for="bg in backgroundShopSets[1].items"
|
v-for="bg in backgroundShopSets[1].items"
|
||||||
:key="bg.key"
|
:key="bg.key"
|
||||||
|
:id="bg.key"
|
||||||
class="col-4 text-center customize-option background-button"
|
class="col-4 text-center customize-option background-button"
|
||||||
:popover-title="bg.text"
|
|
||||||
:popover="bg.notes"
|
|
||||||
popover-trigger="mouseenter"
|
|
||||||
@click="!user.purchased.background[bg.key]
|
@click="!user.purchased.background[bg.key]
|
||||||
? backgroundSelected(bg) : unlock('background.' + bg.key)"
|
? backgroundSelected(bg) : unlock('background.' + bg.key)"
|
||||||
>
|
>
|
||||||
@@ -270,6 +278,13 @@
|
|||||||
:pinned="isBackgroundPinned(bg)"
|
:pinned="isBackgroundPinned(bg)"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
|
<b-popover
|
||||||
|
:target="bg.key"
|
||||||
|
triggers="hover focus"
|
||||||
|
placement="bottom"
|
||||||
|
:prevent-overflow="false"
|
||||||
|
:content="bg.notes"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -302,10 +317,8 @@
|
|||||||
<div
|
<div
|
||||||
v-for="bg in set.items"
|
v-for="bg in set.items"
|
||||||
:key="bg.key"
|
:key="bg.key"
|
||||||
|
:id="bg.key"
|
||||||
class="col-4 text-center customize-option background-button"
|
class="col-4 text-center customize-option background-button"
|
||||||
:popover-title="bg.text"
|
|
||||||
:popover="bg.notes"
|
|
||||||
popover-trigger="mouseenter"
|
|
||||||
@click="!user.purchased.background[bg.key]
|
@click="!user.purchased.background[bg.key]
|
||||||
? backgroundSelected(bg) : unlock('background.' + bg.key)"
|
? backgroundSelected(bg) : unlock('background.' + bg.key)"
|
||||||
>
|
>
|
||||||
@@ -336,6 +349,13 @@
|
|||||||
:pinned="isBackgroundPinned(bg)"
|
:pinned="isBackgroundPinned(bg)"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
|
<b-popover
|
||||||
|
:target="bg.key"
|
||||||
|
triggers="hover focus"
|
||||||
|
placement="bottom"
|
||||||
|
:prevent-overflow="false"
|
||||||
|
:content="bg.notes"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="!ownsSet('background', set.items) && set.identifier !== 'incentiveBackgrounds'"
|
v-if="!ownsSet('background', set.items) && set.identifier !== 'incentiveBackgrounds'"
|
||||||
@@ -358,16 +378,21 @@
|
|||||||
<div
|
<div
|
||||||
v-for="(bg) in ownedBackgrounds"
|
v-for="(bg) in ownedBackgrounds"
|
||||||
:key="bg.key"
|
:key="bg.key"
|
||||||
|
:id="bg.key"
|
||||||
class="col-4 text-center customize-option background-button"
|
class="col-4 text-center customize-option background-button"
|
||||||
:popover-title="bg.text"
|
|
||||||
:popover="bg.notes"
|
|
||||||
popover-trigger="mouseenter"
|
|
||||||
@click="unlock('background.' + bg.key)"
|
@click="unlock('background.' + bg.key)"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="background"
|
class="background"
|
||||||
:class="[`background_${bg.key}`, backgroundLockedStatus(bg.key)]"
|
:class="[`background_${bg.key}`, backgroundLockedStatus(bg.key)]"
|
||||||
></div>
|
></div>
|
||||||
|
<b-popover
|
||||||
|
:target="bg.key"
|
||||||
|
triggers="hover focus"
|
||||||
|
placement="bottom"
|
||||||
|
:prevent-overflow="false"
|
||||||
|
:content="bg.notes"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
209
website/client/src/components/externalLinkModal.vue
Normal file
209
website/client/src/components/externalLinkModal.vue
Normal file
@@ -0,0 +1,209 @@
|
|||||||
|
<template>
|
||||||
|
<b-modal
|
||||||
|
id="external-link-modal"
|
||||||
|
size="md"
|
||||||
|
>
|
||||||
|
<!-- HEADER -->
|
||||||
|
<div slot="modal-header">
|
||||||
|
<div
|
||||||
|
class="modal-close"
|
||||||
|
@click="close()"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="icon-close"
|
||||||
|
v-html="icons.close"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="exclamation-container d-flex align-items-center justify-content-center">
|
||||||
|
<div
|
||||||
|
v-once
|
||||||
|
class="svg-icon svg-exclamation"
|
||||||
|
v-html="icons.exclamation"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
<h2>
|
||||||
|
{{ $t('leaveHabitica') }}
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- BODY -->
|
||||||
|
<div
|
||||||
|
class="row leave-warning-text"
|
||||||
|
v-html="$t('leaveHabiticaText')"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="skip-modal"
|
||||||
|
>
|
||||||
|
{{ $t('skipExternalLinkModal') }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- FOOTER -->
|
||||||
|
<div slot="modal-footer">
|
||||||
|
<button
|
||||||
|
v-once
|
||||||
|
class="btn btn-primary"
|
||||||
|
@click="proceed()"
|
||||||
|
>
|
||||||
|
{{ $t('continue') }}
|
||||||
|
</button>
|
||||||
|
<div
|
||||||
|
v-once
|
||||||
|
class="close-link justify-content-center"
|
||||||
|
@click="close()"
|
||||||
|
>
|
||||||
|
{{ $t('cancel') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</b-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
@import '~@/assets/scss/colors.scss';
|
||||||
|
|
||||||
|
#external-link-modal {
|
||||||
|
&.modal {
|
||||||
|
display: flex !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-md {
|
||||||
|
max-width: 448px;
|
||||||
|
min-width: 330px;
|
||||||
|
margin: auto;
|
||||||
|
|
||||||
|
.modal-close {
|
||||||
|
position: absolute;
|
||||||
|
right: 12px;
|
||||||
|
top: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
.icon-close {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
vertical-align: middle;
|
||||||
|
|
||||||
|
& svg {
|
||||||
|
fill: $yellow-1;
|
||||||
|
opacity: 0.75;
|
||||||
|
}
|
||||||
|
& :hover {
|
||||||
|
fill: $yellow-1;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header {
|
||||||
|
justify-content: center;
|
||||||
|
padding-top: 32px;
|
||||||
|
padding-bottom: 0px;
|
||||||
|
background: $yellow-100;
|
||||||
|
border-top-right-radius: 8px;
|
||||||
|
border-top-left-radius: 8px;
|
||||||
|
border-bottom: none;
|
||||||
|
|
||||||
|
.exclamation-container {
|
||||||
|
width: 64px;
|
||||||
|
height: 64px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: $yellow-1;
|
||||||
|
margin: 0 auto;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.svg-exclamation {
|
||||||
|
width: 8px;
|
||||||
|
color: $white;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
color: $yellow-1;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-body {
|
||||||
|
padding: 16px 44px 20px 44px;
|
||||||
|
background: $white;
|
||||||
|
|
||||||
|
.leave-warning-text {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
line-height: 1.71;
|
||||||
|
text-align: center;
|
||||||
|
margin-top:24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skip-modal {
|
||||||
|
color: $gray-100;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 1.33;
|
||||||
|
margin-top: 16px;
|
||||||
|
// padding-bottom: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-footer {
|
||||||
|
background: $white;
|
||||||
|
border-bottom-right-radius: 8px;
|
||||||
|
border-bottom-left-radius: 8px;
|
||||||
|
justify-content: center;
|
||||||
|
border-top: none;
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
.close-link {
|
||||||
|
color: $purple-300;
|
||||||
|
line-height: 1.71;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-top:16px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import exclamationIcon from '@/assets/svg/exclamation.svg';
|
||||||
|
import closeIcon from '@/assets/svg/new-close.svg';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
icons: Object.freeze({
|
||||||
|
close: closeIcon,
|
||||||
|
exclamation: exclamationIcon,
|
||||||
|
}),
|
||||||
|
url: '',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
mounted () {
|
||||||
|
this.$root.$on('habitica:external-link', url => {
|
||||||
|
this.url = url;
|
||||||
|
this.$root.$emit('bv::show::modal', 'external-link-modal');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
beforeDestroy () {
|
||||||
|
this.$root.$off('habitica:external-link');
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
close () {
|
||||||
|
this.$root.$emit('bv::hide::modal', 'external-link-modal');
|
||||||
|
},
|
||||||
|
proceed () {
|
||||||
|
window.open(this.url, '_blank').focus();
|
||||||
|
this.close();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -87,6 +87,8 @@
|
|||||||
<script>
|
<script>
|
||||||
import debounce from 'lodash/debounce';
|
import debounce from 'lodash/debounce';
|
||||||
|
|
||||||
|
import externalLinks from '../../mixins/externalLinks';
|
||||||
|
|
||||||
import autocomplete from '../chat/autoComplete';
|
import autocomplete from '../chat/autoComplete';
|
||||||
import communityGuidelines from './communityGuidelines';
|
import communityGuidelines from './communityGuidelines';
|
||||||
import chatMessage from '../chat/chatMessages';
|
import chatMessage from '../chat/chatMessages';
|
||||||
@@ -103,6 +105,7 @@ export default {
|
|||||||
communityGuidelines,
|
communityGuidelines,
|
||||||
chatMessage,
|
chatMessage,
|
||||||
},
|
},
|
||||||
|
mixins: [externalLinks],
|
||||||
props: ['label', 'group', 'placeholder'],
|
props: ['label', 'group', 'placeholder'],
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
@@ -132,6 +135,10 @@ export default {
|
|||||||
},
|
},
|
||||||
mounted () {
|
mounted () {
|
||||||
this.textbox = this.$refs['user-entry'];
|
this.textbox = this.$refs['user-entry'];
|
||||||
|
this.handleExternalLinks();
|
||||||
|
},
|
||||||
|
updated () {
|
||||||
|
this.handleExternalLinks();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
// https://medium.com/@_jh3y/how-to-where-s-the-caret-getting-the-xy-position-of-the-caret-a24ba372990a
|
// https://medium.com/@_jh3y/how-to-where-s-the-caret-getting-the-xy-position-of-the-caret-a24ba372990a
|
||||||
|
|||||||
@@ -11,9 +11,12 @@
|
|||||||
<div class="quest_screen"></div>
|
<div class="quest_screen"></div>
|
||||||
<div class="row heading">
|
<div class="row heading">
|
||||||
<div class="col-12 text-center pr-5 pl-5">
|
<div class="col-12 text-center pr-5 pl-5">
|
||||||
<h2 v-once>
|
<h1
|
||||||
|
v-once
|
||||||
|
class="mb-2"
|
||||||
|
>
|
||||||
{{ $t('playInPartyTitle') }}
|
{{ $t('playInPartyTitle') }}
|
||||||
</h2>
|
</h1>
|
||||||
<p
|
<p
|
||||||
v-once
|
v-once
|
||||||
class="mb-4"
|
class="mb-4"
|
||||||
@@ -22,68 +25,92 @@
|
|||||||
</p>
|
</p>
|
||||||
<button
|
<button
|
||||||
v-once
|
v-once
|
||||||
class="btn btn-primary"
|
class="btn btn-primary px-4 mb-2"
|
||||||
@click="createParty()"
|
@click="createParty()"
|
||||||
>
|
>
|
||||||
{{ $t('createParty') }}
|
{{ $t('createParty') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<close-x
|
||||||
|
@close="close()"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="row grey-row">
|
<div class="row grey-row">
|
||||||
<div class="col-12 text-center">
|
<div class="col-12 text-center px-0">
|
||||||
<div class="join-party"></div>
|
<div class="join-party"></div>
|
||||||
<h2 v-once>
|
<h1
|
||||||
|
v-once
|
||||||
|
class="mb-2"
|
||||||
|
>
|
||||||
{{ $t('wantToJoinPartyTitle') }}
|
{{ $t('wantToJoinPartyTitle') }}
|
||||||
</h2>
|
</h1>
|
||||||
<p v-html="$t('wantToJoinPartyDescription')"></p>
|
<p
|
||||||
<div
|
|
||||||
class="form-group"
|
|
||||||
@click="copyUsername"
|
|
||||||
>
|
|
||||||
<div class="d-flex align-items-center">
|
|
||||||
<label
|
|
||||||
v-once
|
v-once
|
||||||
class="mr-3"
|
class="mb-4"
|
||||||
>{{ $t('username') }}</label>
|
v-html="$t('partyFinderDescription')"
|
||||||
<div class="flex-grow-1">
|
|
||||||
<div class="input-group-prepend input-group-text">
|
|
||||||
@
|
|
||||||
<div class="text">
|
|
||||||
{{ user.auth.local.username }}
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="svg-icon copy-icon"
|
|
||||||
v-html="icons.copy"
|
|
||||||
></div>
|
|
||||||
<div
|
|
||||||
v-once
|
|
||||||
class="small"
|
|
||||||
>
|
>
|
||||||
{{ $t('copy') }}
|
</p>
|
||||||
</div>
|
<div
|
||||||
</div>
|
v-if="seeking"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="green-bar mb-3"
|
||||||
|
>
|
||||||
|
{{ $t('currentlyLookingForParty') }}
|
||||||
|
</div>
|
||||||
|
<div class="d-flex justify-content-center">
|
||||||
|
<div
|
||||||
|
class="red-link"
|
||||||
|
@click="seekParty()"
|
||||||
|
>
|
||||||
|
{{ $t('leave') }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<button
|
||||||
|
v-else
|
||||||
|
class="btn btn-primary px-4 mt-2 mb-1"
|
||||||
|
@click="seekParty()"
|
||||||
|
>
|
||||||
|
{{ $t('lookForParty') }}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</b-modal>
|
</b-modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style>
|
<style lang="scss">
|
||||||
#create-party-modal .modal-body {
|
#create-party-modal {
|
||||||
|
display: flex !important;
|
||||||
|
overflow-y: hidden;
|
||||||
|
|
||||||
|
@media (max-height: 770px) {
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-body {
|
||||||
padding: 0rem 0.75rem;
|
padding: 0rem 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
#create-party-modal .modal-dialog {
|
.modal-content {
|
||||||
width: 35.75rem;
|
border-radius: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#create-party-modal .modal-header {
|
.modal-dialog {
|
||||||
|
width: 566px;
|
||||||
|
margin: auto;
|
||||||
|
|
||||||
|
@media (max-height: 826px) {
|
||||||
|
margin-top: 56px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
border-bottom: 0px;
|
border-bottom: 0px;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@@ -107,15 +134,27 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.green-bar {
|
||||||
|
height: 32px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: bold;
|
||||||
|
line-height: 1.71;
|
||||||
|
text-align: center;
|
||||||
|
color: $green-1;
|
||||||
|
background-color: $green-100;
|
||||||
|
border-radius: 2px;
|
||||||
|
padding: 4px 0px 4px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
.grey-row {
|
.grey-row {
|
||||||
background-color: $gray-700;
|
background-color: $gray-700;
|
||||||
color: #4e4a57;
|
color: #4e4a57;
|
||||||
padding: 2em;
|
padding: 2em;
|
||||||
border-radius: 0px 0px 2px 2px;
|
border-radius: 0px 0px 8px 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
h2 {
|
h1 {
|
||||||
color: $gray-100;
|
color: $purple-300;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-wrap {
|
.header-wrap {
|
||||||
@@ -132,10 +171,6 @@
|
|||||||
border-radius: 2px 2px 0 0;
|
border-radius: 2px 2px 0 0;
|
||||||
image-rendering: optimizequality;
|
image-rendering: optimizequality;
|
||||||
}
|
}
|
||||||
|
|
||||||
h2 {
|
|
||||||
color: $purple-200;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.heading {
|
.heading {
|
||||||
@@ -182,6 +217,21 @@
|
|||||||
margin: 0.75rem auto 0.75rem 0.25rem;
|
margin: 0.75rem auto 0.75rem 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
line-height: 1.71;
|
||||||
|
}
|
||||||
|
|
||||||
|
.red-link {
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.71;
|
||||||
|
text-align: center;
|
||||||
|
color: $maroon-50;
|
||||||
|
&:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.small {
|
.small {
|
||||||
color: $gray-200;
|
color: $gray-200;
|
||||||
margin: auto 0.5rem auto 0.25rem;
|
margin: auto 0.5rem auto 0.25rem;
|
||||||
@@ -192,21 +242,29 @@
|
|||||||
import { mapState } from '@/libs/store';
|
import { mapState } from '@/libs/store';
|
||||||
import * as Analytics from '@/libs/analytics';
|
import * as Analytics from '@/libs/analytics';
|
||||||
import notifications from '@/mixins/notifications';
|
import notifications from '@/mixins/notifications';
|
||||||
|
import closeX from '../ui/closeX';
|
||||||
|
|
||||||
import copyIcon from '@/assets/svg/copy.svg';
|
import copyIcon from '@/assets/svg/copy.svg';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
components: {
|
||||||
|
closeX,
|
||||||
|
},
|
||||||
mixins: [notifications],
|
mixins: [notifications],
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
icons: Object.freeze({
|
icons: Object.freeze({
|
||||||
copy: copyIcon,
|
copy: copyIcon,
|
||||||
}),
|
}),
|
||||||
|
seeking: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState({ user: 'user.data' }),
|
...mapState({ user: 'user.data' }),
|
||||||
},
|
},
|
||||||
|
mounted () {
|
||||||
|
this.seeking = Boolean(this.user.party.seeking);
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async createParty () {
|
async createParty () {
|
||||||
const group = {
|
const group = {
|
||||||
@@ -223,7 +281,10 @@ export default {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.$root.$emit('bv::hide::modal', 'create-party-modal');
|
this.$root.$emit('bv::hide::modal', 'create-party-modal');
|
||||||
this.$router.push('/party');
|
await this.$router.push('/party');
|
||||||
|
},
|
||||||
|
close () {
|
||||||
|
this.$root.$emit('bv::hide::modal', 'create-party-modal');
|
||||||
},
|
},
|
||||||
copyUsername () {
|
copyUsername () {
|
||||||
if (navigator.clipboard) {
|
if (navigator.clipboard) {
|
||||||
@@ -238,6 +299,12 @@ export default {
|
|||||||
}
|
}
|
||||||
this.text(this.$t('usernameCopied'));
|
this.text(this.$t('usernameCopied'));
|
||||||
},
|
},
|
||||||
|
seekParty () {
|
||||||
|
this.$store.dispatch('user:set', {
|
||||||
|
'party.seeking': !this.user.party.seeking ? new Date() : null,
|
||||||
|
});
|
||||||
|
this.seeking = !this.seeking;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -542,7 +542,8 @@ export default {
|
|||||||
await this.$store.dispatch('guilds:leave', data);
|
await this.$store.dispatch('guilds:leave', data);
|
||||||
|
|
||||||
if (this.isParty) {
|
if (this.isParty) {
|
||||||
this.$router.push({ name: 'tasks' });
|
await this.$router.push({ name: 'tasks' });
|
||||||
|
window.location.reload(true);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
upgradeGroup () {
|
upgradeGroup () {
|
||||||
|
|||||||
@@ -4,12 +4,12 @@
|
|||||||
<group-plan-creation-modal />
|
<group-plan-creation-modal />
|
||||||
<div>
|
<div>
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<h1 class="text-center">
|
<h1 v-once class="text-center">
|
||||||
Need more for your Group?
|
{{ $t('groupPlanTitle') }}
|
||||||
</h1>
|
</h1>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-8 offset-2 text-center">
|
<div class="col-8 offset-2 text-center">
|
||||||
<h2 class="sub-text">
|
<h2 v-once class="sub-text">
|
||||||
{{ $t('groupBenefitsDescription') }}
|
{{ $t('groupBenefitsDescription') }}
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
@@ -24,8 +24,8 @@
|
|||||||
src="~@/assets/images/group-plans/group-14@3x.png"
|
src="~@/assets/images/group-plans/group-14@3x.png"
|
||||||
>
|
>
|
||||||
<hr>
|
<hr>
|
||||||
<h2>{{ $t('teamBasedTasks') }}</h2>
|
<h2 v-once> {{ $t('teamBasedTasks') }} </h2>
|
||||||
<p>Set up an easily-viewed shared task list for the group. Assign tasks to your fellow group members, or let them claim their own tasks to make it clear what everyone is working on!</p><!-- eslint-disable-line max-len -->
|
<p v-once> {{ $t('teamBasedTasksListDesc') }} </p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-4">
|
<div class="col-4">
|
||||||
@@ -35,8 +35,8 @@
|
|||||||
src="~@/assets/images/group-plans/group-12@3x.png"
|
src="~@/assets/images/group-plans/group-12@3x.png"
|
||||||
>
|
>
|
||||||
<hr>
|
<hr>
|
||||||
<h2>Group Management Controls</h2>
|
<h2 v-once> {{ $t('groupManagementControls') }} </h2>
|
||||||
<p>Use task approvals to verify that a task that was really completed, add Group Managers to share responsibilities, and enjoy a private group chat for all team members.</p><!-- eslint-disable-line max-len -->
|
<p v-once> {{ $t('groupManagementControlsDesc') }} </p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-4">
|
<div class="col-4">
|
||||||
@@ -46,8 +46,8 @@
|
|||||||
src="~@/assets/images/group-plans/group-13@3x.png"
|
src="~@/assets/images/group-plans/group-13@3x.png"
|
||||||
>
|
>
|
||||||
<hr>
|
<hr>
|
||||||
<h2>In-Game Benefits</h2>
|
<h2 v-once> {{ $t('inGameBenefits') }} </h2>
|
||||||
<p>Group members get an exclusive Jackalope Mount, as well as full subscription benefits, including special monthly equipment sets and the ability to buy gems with gold.</p><!-- eslint-disable-line max-len -->
|
<p v-once> {{ $t('inGameBenefitsDesc') }} </p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
344
website/client/src/components/groups/lookingForParty.vue
Normal file
344
website/client/src/components/groups/lookingForParty.vue
Normal file
@@ -0,0 +1,344 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="d-flex justify-content-center">
|
||||||
|
<div
|
||||||
|
v-if="seekers.length > 0"
|
||||||
|
class="fit-content mx-auto mt-4"
|
||||||
|
>
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<h1 v-once class="my-auto mr-auto"> {{ $t('findPartyMembers') }}</h1>
|
||||||
|
<div
|
||||||
|
class="btn btn-secondary btn-sync ml-auto my-auto pl-2 pr-3 d-flex"
|
||||||
|
@click="refreshList()"
|
||||||
|
>
|
||||||
|
<div class="svg-icon icon-16 color my-auto mr-2" v-html="icons.sync"></div>
|
||||||
|
<div class="ml-auto"> {{ $t('refreshList') }} </div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex flex-wrap seeker-list">
|
||||||
|
<div
|
||||||
|
v-for="(seeker, index) in seekers"
|
||||||
|
:key="seeker._id"
|
||||||
|
class="seeker"
|
||||||
|
>
|
||||||
|
<div class="d-flex">
|
||||||
|
<avatar
|
||||||
|
:member="seeker"
|
||||||
|
:hideClassBadge="true"
|
||||||
|
@click.native="showMemberModal(seeker._id)"
|
||||||
|
class="mr-3 mb-2"
|
||||||
|
/>
|
||||||
|
<div class="card-data">
|
||||||
|
<user-link
|
||||||
|
:user-id="seeker._id"
|
||||||
|
:name="seeker.profile.name"
|
||||||
|
:backer="seeker.backer"
|
||||||
|
:contributor="seeker.contributor"
|
||||||
|
/>
|
||||||
|
<div class="small-with-border pb-2 mb-2">
|
||||||
|
@{{ seeker.auth.local.username }} • {{ $t('level') }} {{ seeker.stats.lvl }}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="d-flex"
|
||||||
|
>
|
||||||
|
<strong v-once> {{ $t('classLabel') }} </strong>
|
||||||
|
<span
|
||||||
|
class="svg-icon d-inline-block icon-16 my-auto mx-2"
|
||||||
|
v-html="icons[seeker.stats.class]"
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
<strong
|
||||||
|
:class="`${seeker.stats.class}-color`"
|
||||||
|
>
|
||||||
|
{{ $t(seeker.stats.class) }}
|
||||||
|
</strong>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<strong v-once class="mr-2"> {{ $t('checkinsLabel') }} </strong>
|
||||||
|
{{ seeker.loginIncentives }}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<strong v-once class="mr-2"> {{ $t('languageLabel') }} </strong>
|
||||||
|
{{ displayLanguage(seeker.preferences.language) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<strong
|
||||||
|
v-if="!seeker.invited"
|
||||||
|
@click="inviteUser(seeker._id, index)"
|
||||||
|
class="btn btn-primary w-100"
|
||||||
|
>
|
||||||
|
{{ $t('inviteToParty') }}
|
||||||
|
</strong>
|
||||||
|
<div
|
||||||
|
v-else
|
||||||
|
@click="rescindInvite(seeker._id, index)"
|
||||||
|
class="btn btn-success w-100"
|
||||||
|
v-html="$t('invitedToYourParty')"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<mugen-scroll
|
||||||
|
v-show="loading"
|
||||||
|
:handler="infiniteScrollTrigger"
|
||||||
|
:should-handle="!loading && canLoadMore"
|
||||||
|
:threshold="1"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="seekers.length === 0 && !loading"
|
||||||
|
class="d-flex flex-column empty-state text-center my-5"
|
||||||
|
>
|
||||||
|
<div class="gray-circle mb-3 mx-auto d-flex">
|
||||||
|
<div
|
||||||
|
class="svg-icon icon-32 color m-auto"
|
||||||
|
v-html="icons.users"
|
||||||
|
>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<strong class="mb-1"> {{ $t('findMorePartyMembers') }} </strong>
|
||||||
|
<div v-html="$t('noOneLooking')"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<h2
|
||||||
|
v-show="loading"
|
||||||
|
class="loading"
|
||||||
|
:class="seekers.length === 0 ? 'mt-3' : 'mt-0'"
|
||||||
|
>
|
||||||
|
{{ $t('loading') }}
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import '~@/assets/scss/colors.scss';
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
color: $purple-300;
|
||||||
|
}
|
||||||
|
|
||||||
|
strong {
|
||||||
|
line-height: 1.71;
|
||||||
|
}
|
||||||
|
.avatar {
|
||||||
|
background-color: $gray-600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-success {
|
||||||
|
box-shadow: none;
|
||||||
|
color: $green-1;
|
||||||
|
font-weight: normal;
|
||||||
|
|
||||||
|
&:not(:disabled):not(.disabled):active {
|
||||||
|
color: $green-1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-sync {
|
||||||
|
min-width: 128px;
|
||||||
|
max-height: 32px;
|
||||||
|
|
||||||
|
.svg-icon {
|
||||||
|
color: $gray-200;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-data {
|
||||||
|
width: 267px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-state {
|
||||||
|
color: $gray-100;
|
||||||
|
line-height: 1.71;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fit-content {
|
||||||
|
width: fit-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gray-circle {
|
||||||
|
width: 64px;
|
||||||
|
height: 64px;
|
||||||
|
color: $gray-600;
|
||||||
|
background-color: $gray-200;
|
||||||
|
border-radius: 100px;
|
||||||
|
|
||||||
|
.icon-32 {
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading {
|
||||||
|
text-align: center;
|
||||||
|
color: $purple-300;
|
||||||
|
}
|
||||||
|
|
||||||
|
.seeker-list {
|
||||||
|
max-width: 920px;
|
||||||
|
|
||||||
|
@media (max-width: 962px) {
|
||||||
|
max-width: 464px;
|
||||||
|
};
|
||||||
|
|
||||||
|
.seeker {
|
||||||
|
width: 448px;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
padding: 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow: 0 1px 3px 0 rgba(26, 24, 29, 0.12), 0 1px 2px 0 rgba(26, 24, 29, 0.24);
|
||||||
|
background-color: $white;
|
||||||
|
|
||||||
|
&:first-of-type {
|
||||||
|
margin-top: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 963px) {
|
||||||
|
&:nth-child(2) {
|
||||||
|
margin-top: 24px;
|
||||||
|
}
|
||||||
|
&:nth-child(even) {
|
||||||
|
margin-left: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.small-with-border {
|
||||||
|
border-bottom: 1px solid $gray-500;
|
||||||
|
color: $gray-100;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: normal;
|
||||||
|
line-height: 1.33;
|
||||||
|
}
|
||||||
|
|
||||||
|
.healer-color {
|
||||||
|
color: $yellow-10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rogue-color {
|
||||||
|
color: $purple-200;
|
||||||
|
}
|
||||||
|
|
||||||
|
.warrior-color {
|
||||||
|
color: $red-50;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wizard-color {
|
||||||
|
color: $blue-10;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import debounce from 'lodash/debounce';
|
||||||
|
import MugenScroll from 'vue-mugen-scroll';
|
||||||
|
import Avatar from '../avatar';
|
||||||
|
import userLink from '../userLink';
|
||||||
|
import { mapState } from '@/libs/store';
|
||||||
|
|
||||||
|
import syncIcon from '@/assets/svg/sync-2.svg';
|
||||||
|
import usersIcon from '@/assets/svg/users.svg';
|
||||||
|
import warriorIcon from '@/assets/svg/warrior.svg';
|
||||||
|
import rogueIcon from '@/assets/svg/rogue.svg';
|
||||||
|
import healerIcon from '@/assets/svg/healer.svg';
|
||||||
|
import wizardIcon from '@/assets/svg/wizard.svg';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
Avatar,
|
||||||
|
MugenScroll,
|
||||||
|
userLink,
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
canLoadMore: true,
|
||||||
|
loading: true,
|
||||||
|
page: 0,
|
||||||
|
party: {},
|
||||||
|
seekers: [],
|
||||||
|
icons: Object.freeze({
|
||||||
|
warrior: warriorIcon,
|
||||||
|
rogue: rogueIcon,
|
||||||
|
healer: healerIcon,
|
||||||
|
sync: syncIcon,
|
||||||
|
users: usersIcon,
|
||||||
|
wizard: wizardIcon,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapState({
|
||||||
|
availableLanguages: 'i18n.availableLanguages',
|
||||||
|
user: 'user.data',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
async mounted () {
|
||||||
|
try {
|
||||||
|
this.party = await this.$store.dispatch('guilds:getGroup', { groupId: this.user.party._id });
|
||||||
|
} catch {
|
||||||
|
this.$router.push('/');
|
||||||
|
}
|
||||||
|
if (!this.party._id || this.party.leader._id !== this.user._id) {
|
||||||
|
this.$router.push('/');
|
||||||
|
} else {
|
||||||
|
this.$store.dispatch('common:setTitle', {
|
||||||
|
section: this.$t('lookingForPartyTitle'),
|
||||||
|
});
|
||||||
|
this.seekers = await this.$store.dispatch('party:lookingForParty');
|
||||||
|
this.canLoadMore = this.seekers.length === 30;
|
||||||
|
this.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
displayLanguage (languageCode) {
|
||||||
|
const language = this.availableLanguages.find(lang => lang.code === languageCode);
|
||||||
|
if (language) {
|
||||||
|
return language.name;
|
||||||
|
}
|
||||||
|
return languageCode;
|
||||||
|
},
|
||||||
|
infiniteScrollTrigger () {
|
||||||
|
if (this.canLoadMore) {
|
||||||
|
this.loading = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.loadMore();
|
||||||
|
},
|
||||||
|
async inviteUser (userId, index) {
|
||||||
|
await this.$store.dispatch('guilds:invite', {
|
||||||
|
invitationDetails: {
|
||||||
|
inviter: this.user.profile.name,
|
||||||
|
uuids: [userId],
|
||||||
|
},
|
||||||
|
groupId: this.party._id,
|
||||||
|
});
|
||||||
|
this.seekers[index].invited = true;
|
||||||
|
},
|
||||||
|
loadMore: debounce(async function loadMoreDebounce () {
|
||||||
|
this.page += 1;
|
||||||
|
const addlSeekers = await this.$store.dispatch('party:lookingForParty', { page: this.page });
|
||||||
|
this.seekers = this.seekers.concat(addlSeekers);
|
||||||
|
this.canLoadMore = this.seekers.length % 30 === 0;
|
||||||
|
this.loading = false;
|
||||||
|
}, 1000),
|
||||||
|
async refreshList () {
|
||||||
|
this.loading = true;
|
||||||
|
this.page = 0;
|
||||||
|
this.seekers = await this.$store.dispatch('party:lookingForParty');
|
||||||
|
this.canLoadMore = this.seekers.length === 30;
|
||||||
|
this.loading = false;
|
||||||
|
},
|
||||||
|
async rescindInvite (userId, index) {
|
||||||
|
await this.$store.dispatch('members:removeMember', {
|
||||||
|
memberId: userId,
|
||||||
|
groupId: this.party._id,
|
||||||
|
});
|
||||||
|
this.seekers[index].invited = false;
|
||||||
|
},
|
||||||
|
showMemberModal (userId) {
|
||||||
|
this.$router.push({ name: 'userProfile', params: { userId } });
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
<div class="sidebar px-4">
|
<div class="sidebar px-4">
|
||||||
<div>
|
<div>
|
||||||
<div class="buttons-wrapper">
|
<div class="buttons-wrapper">
|
||||||
<div class="button-container button-with-menu-row">
|
<div class="button-container d-flex">
|
||||||
<button
|
<button
|
||||||
v-if="!isMember"
|
v-if="!isMember"
|
||||||
class="btn btn-success btn-success"
|
class="btn btn-success btn-success"
|
||||||
@@ -203,10 +203,6 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.button-with-menu-row {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.menuIcon {
|
.menuIcon {
|
||||||
width: 4px;
|
width: 4px;
|
||||||
height: 1rem;
|
height: 1rem;
|
||||||
|
|||||||
@@ -258,13 +258,22 @@
|
|||||||
:key="hero._id"
|
:key="hero._id"
|
||||||
>
|
>
|
||||||
<td>
|
<td>
|
||||||
<user-link
|
<div
|
||||||
v-if="hasPermission(hero, 'userSupport')"
|
v-if="hasPermission(hero, 'userSupport')"
|
||||||
|
class="width-content"
|
||||||
|
>
|
||||||
|
<user-link
|
||||||
|
:id="hero._id"
|
||||||
:user="hero"
|
:user="hero"
|
||||||
:popover="$t('gamemaster')"
|
|
||||||
popover-trigger="mouseenter"
|
|
||||||
popover-placement="right"
|
|
||||||
/>
|
/>
|
||||||
|
<b-popover
|
||||||
|
:target="hero._id"
|
||||||
|
triggers="hover focus"
|
||||||
|
placement="right"
|
||||||
|
:prevent-overflow="false"
|
||||||
|
:content="$t('gamemaster')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<user-link
|
<user-link
|
||||||
v-else
|
v-else
|
||||||
:user="hero"
|
:user="hero"
|
||||||
@@ -302,6 +311,10 @@
|
|||||||
h4.expand-toggle::after {
|
h4.expand-toggle::after {
|
||||||
margin-left: 5px;
|
margin-left: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.width-content {
|
||||||
|
width: fit-content;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|||||||
@@ -6,13 +6,10 @@
|
|||||||
:style="{height}"
|
:style="{height}"
|
||||||
>
|
>
|
||||||
<slot name="content"></slot>
|
<slot name="content"></slot>
|
||||||
<div
|
<close-x
|
||||||
v-if="canClose"
|
v-if="canClose"
|
||||||
class="close-icon svg-icon icon-12"
|
@close="close()"
|
||||||
|
/>
|
||||||
@click="close()"
|
|
||||||
v-html="icons.close"
|
|
||||||
></div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -30,32 +27,24 @@ body.modal-open .habitica-top-banner {
|
|||||||
padding-left: 1.5rem;
|
padding-left: 1.5rem;
|
||||||
padding-right: 1.625rem;
|
padding-right: 1.625rem;
|
||||||
z-index: 1300;
|
z-index: 1300;
|
||||||
}
|
|
||||||
|
|
||||||
.close-icon.svg-icon {
|
.modal-close {
|
||||||
position: relative;
|
position: unset;
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
opacity: 0.48;
|
|
||||||
|
|
||||||
& ::v-deep svg path {
|
|
||||||
stroke: $white !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
opacity: 0.75;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import closeIcon from '@/assets/svg/close.svg';
|
import closeX from '@/components/ui/closeX';
|
||||||
import {
|
import {
|
||||||
clearBannerSetting, hideBanner, isBannerHidden, updateBannerHeight,
|
clearBannerSetting, hideBanner, isBannerHidden, updateBannerHeight,
|
||||||
} from '@/libs/banner.func';
|
} from '@/libs/banner.func';
|
||||||
import { EVENTS } from '@/libs/events';
|
import { EVENTS } from '@/libs/events';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
components: {
|
||||||
|
closeX,
|
||||||
|
},
|
||||||
props: {
|
props: {
|
||||||
bannerId: {
|
bannerId: {
|
||||||
type: String,
|
type: String,
|
||||||
@@ -82,9 +71,6 @@ export default {
|
|||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
icons: Object.freeze({
|
|
||||||
close: closeIcon,
|
|
||||||
}),
|
|
||||||
hidden: false,
|
hidden: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@@ -119,8 +105,6 @@ export default {
|
|||||||
close () {
|
close () {
|
||||||
hideBanner(this.bannerId);
|
hideBanner(this.bannerId);
|
||||||
this.hidden = true;
|
this.hidden = true;
|
||||||
|
|
||||||
this.$root.$emit(EVENTS.BANNER_HIDDEN, this.bannerId);
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
64
website/client/src/components/header/banners/chatBanner.vue
Normal file
64
website/client/src/components/header/banners/chatBanner.vue
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
<template>
|
||||||
|
<base-banner
|
||||||
|
banner-id="chat-warning"
|
||||||
|
banner-class="chat-banner"
|
||||||
|
class="chat-banner"
|
||||||
|
height="3rem"
|
||||||
|
v-if="showChatWarning"
|
||||||
|
:class="{faq: faqPage}"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
slot="content"
|
||||||
|
class="w-100 text-center"
|
||||||
|
v-html="$t('chatSunsetWarning')"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</base-banner>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
@import '~@/assets/scss/colors.scss';
|
||||||
|
|
||||||
|
.chat-banner {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 48px;
|
||||||
|
padding: 8px;
|
||||||
|
color: $orange-1;
|
||||||
|
background-color: $orange-100;
|
||||||
|
line-height: 1.71;
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: $orange-1;
|
||||||
|
text-decoration: underline;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: $orange-1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.faq {
|
||||||
|
position: fixed;
|
||||||
|
top: 3.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import BaseBanner from './base';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
BaseBanner,
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
faqPage () {
|
||||||
|
return (this.$route.fullPath.indexOf('/faq')) !== -1;
|
||||||
|
},
|
||||||
|
showChatWarning () {
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
</script>
|
||||||
@@ -54,17 +54,17 @@
|
|||||||
class="no-party d-none d-md-flex justify-content-center text-center mr-4"
|
class="no-party d-none d-md-flex justify-content-center text-center mr-4"
|
||||||
>
|
>
|
||||||
<div class="align-self-center">
|
<div class="align-self-center">
|
||||||
<h3>{{ $t('battleWithFriends') }}</h3>
|
<h3>{{ user.party._id ? $t('questWithOthers') : $t('battleWithFriends') }}</h3>
|
||||||
<span
|
<span
|
||||||
class="small-text"
|
class="small-text"
|
||||||
v-html="$t('inviteFriendsParty')"
|
v-html="user.party._id ? $t('inviteFriendsParty') : $t('startPartyDetail')"
|
||||||
></span>
|
></span>
|
||||||
<br>
|
<br>
|
||||||
<button
|
<button
|
||||||
class="btn btn-primary"
|
class="btn btn-primary"
|
||||||
@click="createOrInviteParty()"
|
@click="createOrInviteParty()"
|
||||||
>
|
>
|
||||||
{{ user.party._id ? $t('inviteFriends') : $t('startAParty') }}
|
{{ user.party._id ? $t('findPartyMembers') : $t('getStarted') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -122,6 +122,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import orderBy from 'lodash/orderBy';
|
import orderBy from 'lodash/orderBy';
|
||||||
|
import * as Analytics from '@/libs/analytics';
|
||||||
import { mapGetters, mapActions } from '@/libs/store';
|
import { mapGetters, mapActions } from '@/libs/store';
|
||||||
import MemberDetails from '../memberDetails';
|
import MemberDetails from '../memberDetails';
|
||||||
import createPartyModal from '../groups/createPartyModal';
|
import createPartyModal from '../groups/createPartyModal';
|
||||||
@@ -232,10 +233,24 @@ export default {
|
|||||||
this.expandedMember = memberId;
|
this.expandedMember = memberId;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
createOrInviteParty () {
|
async createOrInviteParty () {
|
||||||
if (this.user.party._id) {
|
if (this.user.party._id) {
|
||||||
this.$root.$emit('inviteModal::inviteToGroup', this.user.party);
|
await Analytics.track({
|
||||||
|
eventName: 'Header Party CTA',
|
||||||
|
eventAction: 'Header Party CTA',
|
||||||
|
eventCategory: 'behavior',
|
||||||
|
hitType: 'event',
|
||||||
|
state: 'Find Party Members',
|
||||||
|
});
|
||||||
|
this.$router.push('/looking-for-party');
|
||||||
} else {
|
} else {
|
||||||
|
await Analytics.track({
|
||||||
|
eventName: 'Header Party CTA',
|
||||||
|
eventAction: 'Header Party CTA',
|
||||||
|
eventCategory: 'behavior',
|
||||||
|
hitType: 'event',
|
||||||
|
state: 'Get Started',
|
||||||
|
});
|
||||||
this.$root.$emit('bv::show::modal', 'create-party-modal');
|
this.$root.$emit('bv::show::modal', 'create-party-modal');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -148,7 +148,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<b-nav-item
|
<b-nav-item
|
||||||
v-if="user.party._id"
|
v-if="user.party._id && user._id !== partyLeaderId"
|
||||||
class="topbar-item"
|
class="topbar-item"
|
||||||
:class="{'active': $route.path.startsWith('/party')}"
|
:class="{'active': $route.path.startsWith('/party')}"
|
||||||
tag="li"
|
tag="li"
|
||||||
@@ -156,18 +156,10 @@
|
|||||||
>
|
>
|
||||||
{{ $t('party') }}
|
{{ $t('party') }}
|
||||||
</b-nav-item>
|
</b-nav-item>
|
||||||
<b-nav-item
|
|
||||||
v-if="!user.party._id"
|
|
||||||
class="topbar-item"
|
|
||||||
:class="{'active': $route.path.startsWith('/party')}"
|
|
||||||
@click="openPartyModal()"
|
|
||||||
>
|
|
||||||
{{ $t('party') }}
|
|
||||||
</b-nav-item>
|
|
||||||
<li
|
<li
|
||||||
|
v-if="user.party._id && user._id === partyLeaderId"
|
||||||
class="topbar-item droppable"
|
class="topbar-item droppable"
|
||||||
:class="{
|
:class="{'active': $route.path.startsWith('/party')}"
|
||||||
'active': $route.path.startsWith('/groups')}"
|
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="chevron rotate"
|
class="chevron rotate"
|
||||||
@@ -181,31 +173,27 @@
|
|||||||
</div>
|
</div>
|
||||||
<router-link
|
<router-link
|
||||||
class="nav-link"
|
class="nav-link"
|
||||||
:to="{name: 'tavern'}"
|
:to="{name: 'party'}"
|
||||||
>
|
>
|
||||||
{{ $t('guilds') }}
|
{{ $t('party') }}
|
||||||
</router-link>
|
</router-link>
|
||||||
<div class="topbar-dropdown">
|
<div class="topbar-dropdown">
|
||||||
<router-link
|
<router-link
|
||||||
class="topbar-dropdown-item dropdown-item"
|
class="topbar-dropdown-item dropdown-item"
|
||||||
:to="{name: 'tavern'}"
|
:to="{name: 'lookingForParty'}"
|
||||||
>
|
>
|
||||||
{{ $t('tavern') }}
|
{{ $t('lookingForPartyTitle') }}
|
||||||
</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>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
|
<b-nav-item
|
||||||
|
v-if="!user.party._id"
|
||||||
|
class="topbar-item"
|
||||||
|
:class="{'active': $route.path.startsWith('/party')}"
|
||||||
|
@click="openPartyModal()"
|
||||||
|
>
|
||||||
|
{{ $t('party') }}
|
||||||
|
</b-nav-item>
|
||||||
<li
|
<li
|
||||||
class="topbar-item droppable"
|
class="topbar-item droppable"
|
||||||
:class="{
|
:class="{
|
||||||
@@ -324,22 +312,18 @@
|
|||||||
>
|
>
|
||||||
{{ $t('reportBug') }}
|
{{ $t('reportBug') }}
|
||||||
</a>
|
</a>
|
||||||
<router-link
|
<a
|
||||||
class="topbar-dropdown-item dropdown-item"
|
class="topbar-dropdown-item dropdown-item"
|
||||||
to="/groups/guild/5481ccf3-5d2d-48a9-a871-70a7380cee5a"
|
target="_blank"
|
||||||
|
@click.prevent="openBugReportModal(true)"
|
||||||
>
|
>
|
||||||
{{ $t('askQuestion') }}
|
{{ $t('askQuestion') }}
|
||||||
</router-link>
|
</a>
|
||||||
<a
|
<a
|
||||||
class="topbar-dropdown-item dropdown-item"
|
class="topbar-dropdown-item dropdown-item"
|
||||||
href="https://docs.google.com/forms/d/e/1FAIpQLScPhrwq_7P1C6PTrI3lbvTsvqGyTNnGzp1ugi1Ml0PFee_p5g/viewform?usp=sf_link"
|
href="https://docs.google.com/forms/d/e/1FAIpQLScPhrwq_7P1C6PTrI3lbvTsvqGyTNnGzp1ugi1Ml0PFee_p5g/viewform?usp=sf_link"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>{{ $t('requestFeature') }}</a>
|
>{{ $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
|
<a
|
||||||
class="topbar-dropdown-item dropdown-item"
|
class="topbar-dropdown-item dropdown-item"
|
||||||
href="https://habitica.fandom.com/wiki/Habitica_Wiki"
|
href="https://habitica.fandom.com/wiki/Habitica_Wiki"
|
||||||
@@ -631,6 +615,7 @@ body.modal-open #habitica-menu {
|
|||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: $purple-300;
|
background: $purple-300;
|
||||||
|
text-decoration: none;
|
||||||
|
|
||||||
&:last-child {
|
&:last-child {
|
||||||
border-bottom-right-radius: 5px;
|
border-bottom-right-radius: 5px;
|
||||||
@@ -768,6 +753,7 @@ export default {
|
|||||||
return {
|
return {
|
||||||
isUserDropdownOpen: false,
|
isUserDropdownOpen: false,
|
||||||
menuIsOpen: false,
|
menuIsOpen: false,
|
||||||
|
partyLeaderId: null,
|
||||||
icons: Object.freeze({
|
icons: Object.freeze({
|
||||||
gem: gemIcon,
|
gem: gemIcon,
|
||||||
gold: goldIcon,
|
gold: goldIcon,
|
||||||
@@ -796,15 +782,21 @@ export default {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
mounted () {
|
async mounted () {
|
||||||
this.getUserGroupPlans();
|
await this.getUserGroupPlans();
|
||||||
|
await this.getUserParty();
|
||||||
|
if (document.getElementById('menu_collapse')) {
|
||||||
Array.from(document.getElementById('menu_collapse').getElementsByTagName('a')).forEach(link => {
|
Array.from(document.getElementById('menu_collapse').getElementsByTagName('a')).forEach(link => {
|
||||||
link.addEventListener('click', this.closeMenu);
|
link.addEventListener('click', this.closeMenu);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
Array.from(document.getElementsByClassName('topbar-item')).forEach(link => {
|
Array.from(document.getElementsByClassName('topbar-item')).forEach(link => {
|
||||||
link.addEventListener('mouseenter', this.dropdownDesktop);
|
link.addEventListener('mouseenter', this.dropdownDesktop);
|
||||||
link.addEventListener('mouseleave', this.dropdownDesktop);
|
link.addEventListener('mouseleave', this.dropdownDesktop);
|
||||||
});
|
});
|
||||||
|
this.$root.$on('update-party', () => {
|
||||||
|
this.getUserParty();
|
||||||
|
});
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
modForm () {
|
modForm () {
|
||||||
@@ -816,6 +808,12 @@ export default {
|
|||||||
async getUserGroupPlans () {
|
async getUserGroupPlans () {
|
||||||
await this.$store.dispatch('guilds:getGroupPlans');
|
await this.$store.dispatch('guilds:getGroupPlans');
|
||||||
},
|
},
|
||||||
|
async getUserParty () {
|
||||||
|
if (this.user.party._id) {
|
||||||
|
await this.$store.dispatch('party:getParty');
|
||||||
|
this.partyLeaderId = this.$store.state.party.data.leader._id;
|
||||||
|
}
|
||||||
|
},
|
||||||
openPartyModal () {
|
openPartyModal () {
|
||||||
this.$root.$emit('bv::show::modal', 'create-party-modal');
|
this.$root.$emit('bv::show::modal', 'create-party-modal');
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -44,13 +44,13 @@ export default {
|
|||||||
if (!this.notification || !this.notification.data) {
|
if (!this.notification || !this.notification.data) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this.notification.data.destination === 'backgrounds') {
|
if (this.notification.data.destination.indexOf('backgrounds') !== -1) {
|
||||||
this.$store.state.avatarEditorOptions.editingUser = true;
|
this.$store.state.avatarEditorOptions.editingUser = true;
|
||||||
this.$store.state.avatarEditorOptions.startingPage = 'backgrounds';
|
this.$store.state.avatarEditorOptions.startingPage = 'backgrounds';
|
||||||
this.$store.state.avatarEditorOptions.subpage = '2023';
|
this.$store.state.avatarEditorOptions.subpage = '2023';
|
||||||
this.$root.$emit('bv::show::modal', 'avatar-modal');
|
this.$root.$emit('bv::show::modal', 'avatar-modal');
|
||||||
} else {
|
} else {
|
||||||
this.$router.push({ name: this.notification.data.destination || 'items' });
|
this.$router.push(this.notification.data.destination || '/inventory/items');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -34,11 +34,13 @@
|
|||||||
<script>
|
<script>
|
||||||
import BaseNotification from './base';
|
import BaseNotification from './base';
|
||||||
import { mapState } from '@/libs/store';
|
import { mapState } from '@/libs/store';
|
||||||
|
import sync from '@/mixins/sync';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
BaseNotification,
|
BaseNotification,
|
||||||
},
|
},
|
||||||
|
mixins: [sync],
|
||||||
props: {
|
props: {
|
||||||
notification: {
|
notification: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@@ -73,6 +75,7 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await this.$store.dispatch('guilds:join', { groupId: group.id, type: 'party' });
|
await this.$store.dispatch('guilds:join', { groupId: group.id, type: 'party' });
|
||||||
|
this.sync();
|
||||||
this.$router.push('/party');
|
this.$router.push('/party');
|
||||||
},
|
},
|
||||||
reject () {
|
reject () {
|
||||||
|
|||||||
@@ -270,6 +270,9 @@ export default {
|
|||||||
methods: {
|
methods: {
|
||||||
percent,
|
percent,
|
||||||
showMemberModal (member) {
|
showMemberModal (member) {
|
||||||
|
if (this.$route.name === 'userProfile' && this.$route.params?.userId === member._id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.$router.push({ name: 'userProfile', params: { userId: member._id } });
|
this.$router.push({ name: 'userProfile', params: { userId: member._id } });
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -139,6 +139,8 @@
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
|
||||||
|
import externalLinks from '../../mixins/externalLinks';
|
||||||
|
|
||||||
import renderWithMentions from '@/libs/renderWithMentions';
|
import renderWithMentions from '@/libs/renderWithMentions';
|
||||||
import { mapState } from '@/libs/store';
|
import { mapState } from '@/libs/store';
|
||||||
import userLink from '../userLink';
|
import userLink from '../userLink';
|
||||||
@@ -150,6 +152,7 @@ export default {
|
|||||||
components: {
|
components: {
|
||||||
userLink,
|
userLink,
|
||||||
},
|
},
|
||||||
|
mixins: [externalLinks],
|
||||||
filters: {
|
filters: {
|
||||||
timeAgo (value) {
|
timeAgo (value) {
|
||||||
return moment(value).fromNow();
|
return moment(value).fromNow();
|
||||||
@@ -179,6 +182,10 @@ export default {
|
|||||||
},
|
},
|
||||||
mounted () {
|
mounted () {
|
||||||
this.$emit('message-card-mounted');
|
this.$emit('message-card-mounted');
|
||||||
|
this.handleExternalLinks();
|
||||||
|
},
|
||||||
|
updated () {
|
||||||
|
this.handleExternalLinks();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
report () {
|
report () {
|
||||||
|
|||||||
@@ -11,8 +11,6 @@
|
|||||||
<low-health />
|
<low-health />
|
||||||
<level-up />
|
<level-up />
|
||||||
<choose-class />
|
<choose-class />
|
||||||
<testing />
|
|
||||||
<testingletiant />
|
|
||||||
<rebirth-enabled />
|
<rebirth-enabled />
|
||||||
<contributor />
|
<contributor />
|
||||||
<won-challenge />
|
<won-challenge />
|
||||||
@@ -127,8 +125,6 @@ import chooseClass from './achievements/chooseClass';
|
|||||||
import armoireEmpty from './achievements/armoireEmpty';
|
import armoireEmpty from './achievements/armoireEmpty';
|
||||||
import questCompleted from './achievements/questCompleted';
|
import questCompleted from './achievements/questCompleted';
|
||||||
import questInvitation from './achievements/questInvitation';
|
import questInvitation from './achievements/questInvitation';
|
||||||
import testing from './achievements/testing';
|
|
||||||
import testingletiant from './achievements/testingletiant';
|
|
||||||
import rebirthEnabled from './achievements/rebirthEnabled';
|
import rebirthEnabled from './achievements/rebirthEnabled';
|
||||||
import contributor from './achievements/contributor';
|
import contributor from './achievements/contributor';
|
||||||
import invitedFriend from './achievements/invitedFriend';
|
import invitedFriend from './achievements/invitedFriend';
|
||||||
@@ -269,8 +265,6 @@ export default {
|
|||||||
armoireEmpty,
|
armoireEmpty,
|
||||||
questCompleted,
|
questCompleted,
|
||||||
questInvitation,
|
questInvitation,
|
||||||
testing,
|
|
||||||
testingletiant,
|
|
||||||
rebirthEnabled,
|
rebirthEnabled,
|
||||||
contributor,
|
contributor,
|
||||||
loginIncentives,
|
loginIncentives,
|
||||||
@@ -300,7 +294,6 @@ export default {
|
|||||||
// general notifications
|
// general notifications
|
||||||
'CRON',
|
'CRON',
|
||||||
'FIRST_DROPS',
|
'FIRST_DROPS',
|
||||||
'GUILD_PROMPT',
|
|
||||||
'LOGIN_INCENTIVE',
|
'LOGIN_INCENTIVE',
|
||||||
'NEW_CONTRIBUTOR_LEVEL',
|
'NEW_CONTRIBUTOR_LEVEL',
|
||||||
'ONBOARDING_COMPLETE',
|
'ONBOARDING_COMPLETE',
|
||||||
@@ -705,14 +698,6 @@ export default {
|
|||||||
this.$root.$emit('bv::show::modal', 'first-drops');
|
this.$root.$emit('bv::show::modal', 'first-drops');
|
||||||
}
|
}
|
||||||
break;
|
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':
|
case 'REBIRTH_ENABLED':
|
||||||
this.$root.$emit('bv::show::modal', 'rebirth-enabled');
|
this.$root.$emit('bv::show::modal', 'rebirth-enabled');
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -126,7 +126,7 @@
|
|||||||
|
|
||||||
<!-- the word "total" -->
|
<!-- the word "total" -->
|
||||||
<div class="buy-gem-total">
|
<div class="buy-gem-total">
|
||||||
{{ $t('sendGiftTotal') }}
|
{{ $t('sendTotal') }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- the actual dollar amount -->
|
<!-- the actual dollar amount -->
|
||||||
|
|||||||
@@ -179,7 +179,9 @@ export default {
|
|||||||
let valid = true;
|
let valid = true;
|
||||||
|
|
||||||
for (const stat of canRestore) {
|
for (const stat of canRestore) {
|
||||||
if (this.restoreValues.stats[stat] === '') {
|
if (this.restoreValues.stats[stat] === ''
|
||||||
|
|| this.restoreValues.stats[stat] < 0
|
||||||
|
) {
|
||||||
this.restoreValues.stats[stat] = this.user.stats[stat];
|
this.restoreValues.stats[stat] = this.user.stats[stat];
|
||||||
valid = false;
|
valid = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -128,7 +128,10 @@
|
|||||||
<hr>
|
<hr>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div class="checkbox">
|
<div
|
||||||
|
class="checkbox"
|
||||||
|
id="preferenceAdvancedCollapsed"
|
||||||
|
>
|
||||||
<label>
|
<label>
|
||||||
<input
|
<input
|
||||||
v-model="user.preferences.advancedCollapsed"
|
v-model="user.preferences.advancedCollapsed"
|
||||||
@@ -136,33 +139,22 @@
|
|||||||
class="mr-2"
|
class="mr-2"
|
||||||
@change="set('advancedCollapsed')"
|
@change="set('advancedCollapsed')"
|
||||||
>
|
>
|
||||||
<span
|
<span class="hint">
|
||||||
class="hint"
|
{{ $t('startAdvCollapsed') }}
|
||||||
popover-trigger="mouseenter"
|
</span>
|
||||||
popover-placement="right"
|
<b-popover
|
||||||
:popover="$t('startAdvCollapsedPop')"
|
target="preferenceAdvancedCollapsed"
|
||||||
>{{ $t('startAdvCollapsed') }}</span>
|
triggers="hover focus"
|
||||||
</label>
|
placement="right"
|
||||||
</div>
|
:prevent-overflow="false"
|
||||||
<div class="checkbox">
|
:content="$t('startAdvCollapsedPop')"
|
||||||
<label>
|
/>
|
||||||
<input
|
|
||||||
v-model="user.preferences.dailyDueDefaultView"
|
|
||||||
type="checkbox"
|
|
||||||
class="mr-2"
|
|
||||||
@change="set('dailyDueDefaultView')"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
class="hint"
|
|
||||||
popover-trigger="mouseenter"
|
|
||||||
popover-placement="right"
|
|
||||||
:popover="$t('dailyDueDefaultViewPop')"
|
|
||||||
>{{ $t('dailyDueDefaultView') }}</span>
|
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="party.memberCount === 1"
|
v-if="party.memberCount === 1"
|
||||||
class="checkbox"
|
class="checkbox"
|
||||||
|
id="preferenceDisplayInviteAtOneMember"
|
||||||
>
|
>
|
||||||
<label>
|
<label>
|
||||||
<input
|
<input
|
||||||
@@ -171,12 +163,9 @@
|
|||||||
class="mr-2"
|
class="mr-2"
|
||||||
@change="set('displayInviteToPartyWhenPartyIs1')"
|
@change="set('displayInviteToPartyWhenPartyIs1')"
|
||||||
>
|
>
|
||||||
<span
|
<span class="hint">
|
||||||
class="hint"
|
{{ $t('displayInviteToPartyWhenPartyIs1') }}
|
||||||
popover-trigger="mouseenter"
|
</span>
|
||||||
popover-placement="right"
|
|
||||||
:popover="$t('displayInviteToPartyWhenPartyIs1')"
|
|
||||||
>{{ $t('displayInviteToPartyWhenPartyIs1') }}</span>
|
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="checkbox">
|
<div class="checkbox">
|
||||||
@@ -217,32 +206,47 @@
|
|||||||
</div>
|
</div>
|
||||||
<hr>
|
<hr>
|
||||||
<button
|
<button
|
||||||
|
id="buttonShowBailey"
|
||||||
class="btn btn-primary mr-2 mb-2"
|
class="btn btn-primary mr-2 mb-2"
|
||||||
popover-trigger="mouseenter"
|
|
||||||
popover-placement="right"
|
|
||||||
:popover="$t('showBaileyPop')"
|
|
||||||
@click="showBailey()"
|
@click="showBailey()"
|
||||||
>
|
>
|
||||||
{{ $t('showBailey') }}
|
{{ $t('showBailey') }}
|
||||||
|
<b-popover
|
||||||
|
target="buttonShowBailey"
|
||||||
|
triggers="hover focus"
|
||||||
|
placement="right"
|
||||||
|
:prevent-overflow="false"
|
||||||
|
:content="$t('showBaileyPop')"
|
||||||
|
/>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
|
id="buttonFCV"
|
||||||
class="btn btn-primary mr-2 mb-2"
|
class="btn btn-primary mr-2 mb-2"
|
||||||
popover-trigger="mouseenter"
|
|
||||||
popover-placement="right"
|
|
||||||
:popover="$t('fixValPop')"
|
|
||||||
@click="openRestoreModal()"
|
@click="openRestoreModal()"
|
||||||
>
|
>
|
||||||
{{ $t('fixVal') }}
|
{{ $t('fixVal') }}
|
||||||
|
<b-popover
|
||||||
|
target="buttonFCV"
|
||||||
|
triggers="hover focus"
|
||||||
|
placement="right"
|
||||||
|
:prevent-overflow="false"
|
||||||
|
:content="$t('fixValPop')"
|
||||||
|
/>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
v-if="user.preferences.disableClasses == true"
|
v-if="user.preferences.disableClasses == true"
|
||||||
|
id="buttonEnableClasses"
|
||||||
class="btn btn-primary mb-2"
|
class="btn btn-primary mb-2"
|
||||||
popover-trigger="mouseenter"
|
|
||||||
popover-placement="right"
|
|
||||||
:popover="$t('enableClassPop')"
|
|
||||||
@click="changeClassForUser(false)"
|
@click="changeClassForUser(false)"
|
||||||
>
|
>
|
||||||
{{ $t('enableClass') }}
|
{{ $t('enableClass') }}
|
||||||
|
<b-popover
|
||||||
|
target="buttonEnableClasses"
|
||||||
|
triggers="hover focus"
|
||||||
|
placement="right"
|
||||||
|
:prevent-overflow="false"
|
||||||
|
:content="$t('enableClassPop')"
|
||||||
|
/>
|
||||||
</button>
|
</button>
|
||||||
<hr>
|
<hr>
|
||||||
<day-start-adjustment />
|
<day-start-adjustment />
|
||||||
@@ -532,6 +536,10 @@
|
|||||||
input {
|
input {
|
||||||
color: $gray-50;
|
color: $gray-50;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.checkbox {
|
||||||
|
width: fit-content;
|
||||||
|
}
|
||||||
.usersettings h5 {
|
.usersettings h5 {
|
||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user