mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-16 14:17:22 +01:00
Compare commits
863 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bd83d6f5aa | ||
|
|
9c241a6159 | ||
|
|
7a6baeadbd | ||
|
|
5495acea96 | ||
|
|
a3aa2cc175 | ||
|
|
8b0d02a16b | ||
|
|
c3c0eb974a | ||
|
|
56539100e3 | ||
|
|
b706db43e4 | ||
|
|
3e0a7c70ed | ||
|
|
6a5bd1b0a5 | ||
|
|
7f8a9be766 | ||
|
|
dbdf679e4a | ||
|
|
eb28dfadf9 | ||
|
|
ede28ac33a | ||
|
|
33b249d078 | ||
|
|
a85282763f | ||
|
|
ba9d7b3b5e | ||
|
|
3b794c017a | ||
|
|
47dbe4561f | ||
|
|
97135a1ac3 | ||
|
|
a636e15d11 | ||
|
|
3cc15e869e | ||
|
|
88c8b92a68 | ||
|
|
cee4d7e87b | ||
|
|
9488ec2eb0 | ||
|
|
4fe6c8db64 | ||
|
|
ccf8e0b320 | ||
|
|
1dc558ddba | ||
|
|
ae27ae0090 | ||
|
|
47c2a3a21a | ||
|
|
5d4e1362bb | ||
|
|
25cecf298f | ||
|
|
2de3b63e87 | ||
|
|
7abb8a81a7 | ||
|
|
3eb3891899 | ||
|
|
4b0ad422f1 | ||
|
|
3c603e3bb1 | ||
|
|
4ee788f541 | ||
|
|
99ab9726b4 | ||
|
|
23dd402e79 | ||
|
|
6bd90807f3 | ||
|
|
563a5845f0 | ||
|
|
2e580baf27 | ||
|
|
44ded25f6d | ||
|
|
70da5940a7 | ||
|
|
12aa8a78c1 | ||
|
|
94619737e8 | ||
|
|
ccc9e6611c | ||
|
|
1f1459b0d8 | ||
|
|
6489e74b6b | ||
|
|
c1e264955f | ||
|
|
f302d15bc4 | ||
|
|
8c70c8839b | ||
|
|
3fcd04fd8a | ||
|
|
d85f18751c | ||
|
|
1390c4eae5 | ||
|
|
18ade8ca65 | ||
|
|
7b026fa32c | ||
|
|
33698c219f | ||
|
|
b76d731cee | ||
|
|
4d1ac51543 | ||
|
|
3818fbdd3e | ||
|
|
af245b63d9 | ||
|
|
028da1d6a9 | ||
|
|
49397244c4 | ||
|
|
2b04ed3246 | ||
|
|
51aebb540c | ||
|
|
f5d7777b2c | ||
|
|
be1ffbd671 | ||
|
|
5640139ef1 | ||
|
|
0959499450 | ||
|
|
90ffe587dd | ||
|
|
38aafb6c7b | ||
|
|
ecfcf09184 | ||
|
|
7083dc7e05 | ||
|
|
d4e0417c48 | ||
|
|
ec7c25de9f | ||
|
|
6f9db87843 | ||
|
|
46c9038f54 | ||
|
|
1ce09aeb34 | ||
|
|
2ba327ef14 | ||
|
|
de93b47493 | ||
|
|
b0a21e116a | ||
|
|
53d1a5f9dc | ||
|
|
274f942b1e | ||
|
|
4aad52242c | ||
|
|
166a48e139 | ||
|
|
13de97dde6 | ||
|
|
6d8407ff94 | ||
|
|
663b794435 | ||
|
|
c0276e3663 | ||
|
|
6d57ce3050 | ||
|
|
2159df785f | ||
|
|
9762258975 | ||
|
|
deea64e839 | ||
|
|
9e615ba862 | ||
|
|
d34beca3cc | ||
|
|
07ed989862 | ||
|
|
049844ea7d | ||
|
|
ff4c76165a | ||
|
|
c3220e7c03 | ||
|
|
cb4c6b3ca6 | ||
|
|
ba36ba0157 | ||
|
|
dd95acf436 | ||
|
|
a73b03452a | ||
|
|
935fa1baae | ||
|
|
745f930749 | ||
|
|
d87db40c52 | ||
|
|
0ea91016f8 | ||
|
|
d4f634c3d8 | ||
|
|
286566fc0c | ||
|
|
2ed4df0b7c | ||
|
|
9bb7c6ece0 | ||
|
|
db0a6f6bb8 | ||
|
|
b6305826be | ||
|
|
f00ab86eff | ||
|
|
a44f29dad8 | ||
|
|
67b396bf16 | ||
|
|
ce14a9dadb | ||
|
|
183c90ac3a | ||
|
|
9e1a262f96 | ||
|
|
06dd9fe859 | ||
|
|
2a2c525c2d | ||
|
|
b2c1c9d9dc | ||
|
|
c33eba6736 | ||
|
|
56434cce71 | ||
|
|
c41123c36c | ||
|
|
043a6cd4ba | ||
|
|
0ca2f9034f | ||
|
|
4c7157807b | ||
|
|
0afe797bae | ||
|
|
1c8797e473 | ||
|
|
e0bf6d2e55 | ||
|
|
e96d0659cb | ||
|
|
72d70236ea | ||
|
|
ee2fc8c763 | ||
|
|
b53c03bca8 | ||
|
|
9545f692ef | ||
|
|
0112bd9b5a | ||
|
|
d235576e18 | ||
|
|
3d5d5da933 | ||
|
|
9b19477e2f | ||
|
|
5a9c95f07e | ||
|
|
3000e2b72c | ||
|
|
c1f6f0398e | ||
|
|
cb6488fa05 | ||
|
|
126d90f471 | ||
|
|
98d4fb0f34 | ||
|
|
d3ee3ca53d | ||
|
|
7eac5cebf5 | ||
|
|
6a109adbc5 | ||
|
|
587847f5e9 | ||
|
|
7842cd8a41 | ||
|
|
2f9cf02932 | ||
|
|
daa796454c | ||
|
|
c531239618 | ||
|
|
f6ac7b890a | ||
|
|
229e39facf | ||
|
|
75b00ce2df | ||
|
|
4576353f26 | ||
|
|
acf4b4da63 | ||
|
|
8b5933177a | ||
|
|
a6ddd6d233 | ||
|
|
5ca5adc774 | ||
|
|
005ffe850a | ||
|
|
71cb4e8510 | ||
|
|
40244ab81b | ||
|
|
15b65b342a | ||
|
|
7df3aba71b | ||
|
|
6bb535c129 | ||
|
|
e3bf3d29f7 | ||
|
|
df9c42c1b5 | ||
|
|
7e241bb76f | ||
|
|
17fb681671 | ||
|
|
0069aee5b0 | ||
|
|
240dd1b965 | ||
|
|
88e6b2da7c | ||
|
|
6e7f4a231d | ||
|
|
822a0e56af | ||
|
|
da73c5c418 | ||
|
|
2cf8439bd1 | ||
|
|
0e404ad6ba | ||
|
|
b9f709ab30 | ||
|
|
d57c525fab | ||
|
|
9a3a104ba4 | ||
|
|
63bba13b5f | ||
|
|
d90d781740 | ||
|
|
a3bf329c44 | ||
|
|
22a12e37fa | ||
|
|
446e0422c7 | ||
|
|
5220cc1bf3 | ||
|
|
e8976b40f4 | ||
|
|
3b4b459e68 | ||
|
|
bbbdd89ade | ||
|
|
a20c1ba751 | ||
|
|
d725b5be19 | ||
|
|
545b052c10 | ||
|
|
028b9d569d | ||
|
|
85b861c4a9 | ||
|
|
762e87a82a | ||
|
|
b68e69e1a1 | ||
|
|
4764f115b1 | ||
|
|
95c99295c1 | ||
|
|
a7617fa947 | ||
|
|
2da2a47f32 | ||
|
|
8f744565e2 | ||
|
|
714512b0a3 | ||
|
|
9538c86d02 | ||
|
|
afc1ffd90b | ||
|
|
6988875e8a | ||
|
|
6e0b6171c6 | ||
|
|
53bbd93d80 | ||
|
|
75092336c4 | ||
|
|
310bdf8cb5 | ||
|
|
9435a3089a | ||
|
|
bb6dac2e84 | ||
|
|
acf34e2344 | ||
|
|
1aac4c713d | ||
|
|
bb527caa06 | ||
|
|
98bb6fd7ce | ||
|
|
b8c716ff82 | ||
|
|
9830fce760 | ||
|
|
7fccf59f50 | ||
|
|
dd79f2be60 | ||
|
|
fbdcd4b0a3 | ||
|
|
e229bc5042 | ||
|
|
44c7e8c9dc | ||
|
|
c4ffe39ec9 | ||
|
|
dc3d694d0e | ||
|
|
4f0ce77205 | ||
|
|
c28ec24c33 | ||
|
|
54db84fddc | ||
|
|
e7fd2b4c79 | ||
|
|
05640f513e | ||
|
|
b0ebdfeb65 | ||
|
|
6c01db8d81 | ||
|
|
5a3751cbac | ||
|
|
7802e30e80 | ||
|
|
899452279b | ||
|
|
566716e2fe | ||
|
|
2a42bc9450 | ||
|
|
1ef62d1b66 | ||
|
|
355773ecf3 | ||
|
|
2bb5751f33 | ||
|
|
2570c59130 | ||
|
|
2dfcda068b | ||
|
|
507133c76e | ||
|
|
a7c115877f | ||
|
|
1750a0c2e6 | ||
|
|
759ce61492 | ||
|
|
57193bd5f3 | ||
|
|
e1a1b4eab6 | ||
|
|
350894f985 | ||
|
|
0184d774c2 | ||
|
|
d136162d48 | ||
|
|
2be8ddb60d | ||
|
|
3c67f91525 | ||
|
|
c02aadfac4 | ||
|
|
2f956252ab | ||
|
|
341f16cc82 | ||
|
|
ec179182e7 | ||
|
|
b886d7bb33 | ||
|
|
a8f8f4f544 | ||
|
|
4047bf6943 | ||
|
|
a5a985fd00 | ||
|
|
444d6889de | ||
|
|
c56c69d464 | ||
|
|
4b610ba3f1 | ||
|
|
65e3b599e6 | ||
|
|
7caf211bec | ||
|
|
d4bc7c77a9 | ||
|
|
bfaa7c0fea | ||
|
|
8367de34bf | ||
|
|
ea6b78b7ca | ||
|
|
401067bfed | ||
|
|
b457daa616 | ||
|
|
54c2441934 | ||
|
|
9e8807c40d | ||
|
|
9bfbeaf93e | ||
|
|
860efefdb2 | ||
|
|
6310482b9d | ||
|
|
2d4928cd2b | ||
|
|
f3c2c0f901 | ||
|
|
1fc84c2357 | ||
|
|
13cdcedcba | ||
|
|
72f0b8ed7c | ||
|
|
95f9479d7a | ||
|
|
af095d8450 | ||
|
|
470495387c | ||
|
|
bdef1ca23c | ||
|
|
1835804e86 | ||
|
|
cb58994bdf | ||
|
|
44f3b73183 | ||
|
|
7e23fdc22a | ||
|
|
e138d2b67b | ||
|
|
3e3248fecb | ||
|
|
78ee60611a | ||
|
|
3c7aaa605b | ||
|
|
00343da266 | ||
|
|
56d09411d9 | ||
|
|
ae0df2242a | ||
|
|
5b06b28c97 | ||
|
|
c6a3bfb291 | ||
|
|
7797794cd5 | ||
|
|
d9e09a5f3d | ||
|
|
4e73c8513e | ||
|
|
9421fd7ced | ||
|
|
699de64328 | ||
|
|
6f9cbf9ca1 | ||
|
|
a097819b72 | ||
|
|
77f71b5415 | ||
|
|
ced3621dea | ||
|
|
e321d85b3c | ||
|
|
d72b40d5b0 | ||
|
|
54443a2980 | ||
|
|
00dc990974 | ||
|
|
3737aa045d | ||
|
|
b03ddf6f7d | ||
|
|
4ab89fd3e0 | ||
|
|
f1e200c0f5 | ||
|
|
218664dfcc | ||
|
|
a0f29e970d | ||
|
|
200cd66d66 | ||
|
|
dd05a8d608 | ||
|
|
299e88233c | ||
|
|
26bde1f766 | ||
|
|
d95836b881 | ||
|
|
fac81bb9ee | ||
|
|
b323abd225 | ||
|
|
b3870e5f34 | ||
|
|
29dc56c12f | ||
|
|
b62f08d500 | ||
|
|
f62177fb1a | ||
|
|
885f2998ae | ||
|
|
2afd96e11c | ||
|
|
cd92f44365 | ||
|
|
863177902a | ||
|
|
96974461e5 | ||
|
|
8895b70ffa | ||
|
|
03480ebfc7 | ||
|
|
9b8676f02e | ||
|
|
3e7738b5b1 | ||
|
|
33a235b46c | ||
|
|
137d6c1f9d | ||
|
|
1a5e820d88 | ||
|
|
0c7f9ca6bb | ||
|
|
3e6b3ce3ff | ||
|
|
ea5ba965e7 | ||
|
|
7215a550b5 | ||
|
|
3235dfa236 | ||
|
|
9baf7a7c67 | ||
|
|
cd629ef7fa | ||
|
|
9ef7c45241 | ||
|
|
fef3d09f2d | ||
|
|
53c83c585a | ||
|
|
e628c5dc3b | ||
|
|
9eaa531f66 | ||
|
|
3ffea4332e | ||
|
|
4618fd8954 | ||
|
|
791c19b5f1 | ||
|
|
7193cc6bae | ||
|
|
1845bd1e35 | ||
|
|
5f468d16b7 | ||
|
|
20a99e526d | ||
|
|
1e69f42d0f | ||
|
|
9c2f5213cb | ||
|
|
c06d5107ac | ||
|
|
1eb0f5baa5 | ||
|
|
b28189fff5 | ||
|
|
82497e4041 | ||
|
|
2a5e9c0780 | ||
|
|
c8ca67aa64 | ||
|
|
89e4cbcffe | ||
|
|
67564317fb | ||
|
|
dc2269a307 | ||
|
|
0c713ab368 | ||
|
|
be5d776fb4 | ||
|
|
b0051c45b4 | ||
|
|
b82044e07b | ||
|
|
b4c4769208 | ||
|
|
42a05446c0 | ||
|
|
9f7c0b4861 | ||
|
|
4814b0c52b | ||
|
|
67b16d91a3 | ||
|
|
8d444980de | ||
|
|
08ccd595f2 | ||
|
|
6b625a60ab | ||
|
|
6bdb695616 | ||
|
|
b999d46142 | ||
|
|
ce0f5af08d | ||
|
|
c5296d4cb0 | ||
|
|
22b683b1d9 | ||
|
|
229fd06ee3 | ||
|
|
eb8f84aae0 | ||
|
|
69b69e9d27 | ||
|
|
8033e7c0a0 | ||
|
|
e39b80bb9a | ||
|
|
2038d5e7c8 | ||
|
|
46a8ee52d4 | ||
|
|
52064f6b2a | ||
|
|
f15a27a7f1 | ||
|
|
fcf0dd87f9 | ||
|
|
ab974675b9 | ||
|
|
cb612d99d7 | ||
|
|
cb5a47ec7b | ||
|
|
838b9a5822 | ||
|
|
6c65056e2b | ||
|
|
2bf0fdf4a2 | ||
|
|
cd5ff04ee4 | ||
|
|
dbc5b9f850 | ||
|
|
ca3437d676 | ||
|
|
c43ca62bc4 | ||
|
|
eaa91b2a09 | ||
|
|
6259b68b4f | ||
|
|
08073acf11 | ||
|
|
bddafd4392 | ||
|
|
3ab14e4e5a | ||
|
|
d9830950aa | ||
|
|
45696a6273 | ||
|
|
3f92317b9e | ||
|
|
848883736d | ||
|
|
69e0ab11c0 | ||
|
|
d8d7a81edf | ||
|
|
03a09b7546 | ||
|
|
0d2737572d | ||
|
|
28149202db | ||
|
|
4b23cd9f23 | ||
|
|
8aaabdc086 | ||
|
|
6e6ca05352 | ||
|
|
74d8ecc732 | ||
|
|
63c8a09e22 | ||
|
|
865f623c99 | ||
|
|
061968dd1a | ||
|
|
21bc91c3ae | ||
|
|
0bfc6608c1 | ||
|
|
4108a22d78 | ||
|
|
34f6b63968 | ||
|
|
8d1ebff7e9 | ||
|
|
993df72708 | ||
|
|
50d3226a86 | ||
|
|
17ce2febf9 | ||
|
|
0caa195c6f | ||
|
|
f964e3c0a5 | ||
|
|
9d69d4b863 | ||
|
|
19500600bc | ||
|
|
f25fe9e263 | ||
|
|
5f37487c23 | ||
|
|
12fd79059b | ||
|
|
232061a629 | ||
|
|
3fcc1c522d | ||
|
|
8302c50302 | ||
|
|
6eb06fb054 | ||
|
|
286c8c7530 | ||
|
|
47ab8f2073 | ||
|
|
83353f6481 | ||
|
|
9cbd7ad62d | ||
|
|
3485a1d0bc | ||
|
|
2e5106fda1 | ||
|
|
2e5f5714e4 | ||
|
|
3cf7b2c96c | ||
|
|
286db39478 | ||
|
|
4d4c1cfaf3 | ||
|
|
d7ad3efabf | ||
|
|
f8876fe055 | ||
|
|
b973335d69 | ||
|
|
3b6fce0708 | ||
|
|
ff6bd6de71 | ||
|
|
042afe1df3 | ||
|
|
a208ba4aba | ||
|
|
0e958fd306 | ||
|
|
d98fe79e9c | ||
|
|
0e5a811b98 | ||
|
|
a28aea65f8 | ||
|
|
0f92349902 | ||
|
|
d4881cb73a | ||
|
|
b3216fdb85 | ||
|
|
3e37941e0a | ||
|
|
32088767ac | ||
|
|
f4d021ab8c | ||
|
|
8532203717 | ||
|
|
365daba6fc | ||
|
|
70692752c7 | ||
|
|
95d4016678 | ||
|
|
061457b268 | ||
|
|
e7ec9a6d65 | ||
|
|
d1396e7bc6 | ||
|
|
d5cedaa925 | ||
|
|
bea813b318 | ||
|
|
b31268fbc2 | ||
|
|
35727228f0 | ||
|
|
feb7ab8345 | ||
|
|
f4422b8d6c | ||
|
|
2d4dc9e23c | ||
|
|
bba2e71af3 | ||
|
|
26123ac6ae | ||
|
|
addee73e4d | ||
|
|
c39505d41c | ||
|
|
9736ef0d25 | ||
|
|
638259b885 | ||
|
|
c24545cae5 | ||
|
|
6bc70ca471 | ||
|
|
d2f0d7b20b | ||
|
|
b445bc8261 | ||
|
|
31281b43d3 | ||
|
|
a8a915ea8e | ||
|
|
dc3ee25e65 | ||
|
|
3c9f7ff9d8 | ||
|
|
54f57445da | ||
|
|
95ef2b1789 | ||
|
|
4d32977e5c | ||
|
|
7fe2504906 | ||
|
|
b74cee3d21 | ||
|
|
5af7733150 | ||
|
|
824bf62e0a | ||
|
|
ac24a5dddd | ||
|
|
9111f59da4 | ||
|
|
6a550b34df | ||
|
|
0c973b1cf0 | ||
|
|
4170ef5e79 | ||
|
|
506275c30e | ||
|
|
6838b7d0a6 | ||
|
|
e987cd52a6 | ||
|
|
1df8668d38 | ||
|
|
1af42aa7fe | ||
|
|
9dc9faa70d | ||
|
|
7fbcc0a263 | ||
|
|
0450e9c3ae | ||
|
|
15626b8ae1 | ||
|
|
444f393f3a | ||
|
|
d711bf4085 | ||
|
|
7b93e326fc | ||
|
|
c62ea522c0 | ||
|
|
810362a404 | ||
|
|
2e429513da | ||
|
|
c8625cb23f | ||
|
|
ad50f90ba0 | ||
|
|
d5305f74e3 | ||
|
|
5ea20e4c8c | ||
|
|
9e4e2d0b34 | ||
|
|
6456e6b8e3 | ||
|
|
af961ff16c | ||
|
|
eb2a6095c2 | ||
|
|
ee82f5a774 | ||
|
|
f1c8c4c54b | ||
|
|
79b15d7ddf | ||
|
|
f2fed7ea39 | ||
|
|
ca81ff5af6 | ||
|
|
e28c214696 | ||
|
|
792f45e9bd | ||
|
|
a69c0999d3 | ||
|
|
37ff35306c | ||
|
|
fd430e95b2 | ||
|
|
0ea472b3af | ||
|
|
6d0496fbd0 | ||
|
|
fac1889776 | ||
|
|
9ca6da0f75 | ||
|
|
99f50f825a | ||
|
|
2308f2397e | ||
|
|
f10d5110e5 | ||
|
|
5ced47f590 | ||
|
|
24b2bc9aa1 | ||
|
|
aea08a971a | ||
|
|
e3452b3ba7 | ||
|
|
3db666eb9e | ||
|
|
cf5985e38c | ||
|
|
d88a8247d1 | ||
|
|
7e1ae6a571 | ||
|
|
d117117885 | ||
|
|
5dd168eee4 | ||
|
|
a7c9355dd5 | ||
|
|
81d3e8a68f | ||
|
|
e0fbfffbf2 | ||
|
|
36b968a74a | ||
|
|
fff249fb00 | ||
|
|
ce5c6b9517 | ||
|
|
702013f9ed | ||
|
|
3503b307b2 | ||
|
|
f1ec8bbf2c | ||
|
|
58b033db9e | ||
|
|
b0dcc2f6ef | ||
|
|
3377f8a916 | ||
|
|
22f83d09c4 | ||
|
|
496534ab4b | ||
|
|
7325d2020e | ||
|
|
1a0d39e566 | ||
|
|
dc62ab7577 | ||
|
|
c4e5633e48 | ||
|
|
d977656e96 | ||
|
|
b0a980d56e | ||
|
|
3d75c99f8d | ||
|
|
0aa16d7021 | ||
|
|
4fa3046104 | ||
|
|
fda4be01b8 | ||
|
|
67436fbef1 | ||
|
|
ffb92a5faa | ||
|
|
4a44d60fac | ||
|
|
122cc510d3 | ||
|
|
29a7a07d14 | ||
|
|
18783aefe3 | ||
|
|
a5e242759c | ||
|
|
e01c6cc9a6 | ||
|
|
8a75383c43 | ||
|
|
47ebee9ae8 | ||
|
|
4e97355110 | ||
|
|
7045b5c214 | ||
|
|
e41dccf6d5 | ||
|
|
d0815ea9ee | ||
|
|
d0bd62bf02 | ||
|
|
39d7581c6c | ||
|
|
fdf146dd01 | ||
|
|
023b3ffd88 | ||
|
|
f01e552637 | ||
|
|
9f11820a02 | ||
|
|
ca6c7b8e5f | ||
|
|
02f8ba1638 | ||
|
|
8eb7c67f12 | ||
|
|
1f895fda44 | ||
|
|
e87c180e9b | ||
|
|
dbf9fd54be | ||
|
|
c9b99d1ecf | ||
|
|
fd8120c80d | ||
|
|
053e75562f | ||
|
|
1d50027f51 | ||
|
|
7fe74fd06a | ||
|
|
173a8561b6 | ||
|
|
f21bef707b | ||
|
|
9cf2ccf7c4 | ||
|
|
77d75c4669 | ||
|
|
1c17b95146 | ||
|
|
d89fc209d1 | ||
|
|
844c8bb3e6 | ||
|
|
569fb11db8 | ||
|
|
7671347d3a | ||
|
|
dc3a02bc2e | ||
|
|
1d8c126687 | ||
|
|
7ee49a43f2 | ||
|
|
900bc8dfc1 | ||
|
|
ec260016d3 | ||
|
|
ec770fb29e | ||
|
|
c1079e4eae | ||
|
|
3cb5637fd5 | ||
|
|
cf3a0118c9 | ||
|
|
895a383089 | ||
|
|
f730e7b345 | ||
|
|
d8f3d99d59 | ||
|
|
99fb1f6116 | ||
|
|
7c6dce2124 | ||
|
|
c757a3f52d | ||
|
|
ffb318fe8d | ||
|
|
19cd15ed62 | ||
|
|
3495662196 | ||
|
|
908a1340a4 | ||
|
|
31f4610b20 | ||
|
|
5eac2a6697 | ||
|
|
4495539e8c | ||
|
|
42e6f10b08 | ||
|
|
66750e77d1 | ||
|
|
1eb31a4fec | ||
|
|
fdf2dd1f1a | ||
|
|
f098fbcc80 | ||
|
|
5c429d0328 | ||
|
|
08a84ce13d | ||
|
|
f4bf2df4a9 | ||
|
|
ee6ceecc35 | ||
|
|
76c3e51660 | ||
|
|
d40543f4ca | ||
|
|
e1ad19c216 | ||
|
|
a03c6184b3 | ||
|
|
526d0b1a23 | ||
|
|
f523bd424f | ||
|
|
eda76efd28 | ||
|
|
08323f307c | ||
|
|
1c301f8328 | ||
|
|
a515168766 | ||
|
|
927a08defd | ||
|
|
c8d5eb9689 | ||
|
|
ec298291d4 | ||
|
|
356f2c7b7f | ||
|
|
b12bf773f1 | ||
|
|
d70ff4e5a3 | ||
|
|
a68b02d403 | ||
|
|
2db5ab2352 | ||
|
|
2a43df34c0 | ||
|
|
a317b351be | ||
|
|
4759764e61 | ||
|
|
5aea8def3b | ||
|
|
dde63b619f | ||
|
|
cbdb0bc3e3 | ||
|
|
6edd1a1fa5 | ||
|
|
6fcf739c89 | ||
|
|
f128f3d3cd | ||
|
|
744090e652 | ||
|
|
9c3b367b29 | ||
|
|
90451fbec8 | ||
|
|
64addcf847 | ||
|
|
5263b5e42f | ||
|
|
81fc727d41 | ||
|
|
7a74d4c296 | ||
|
|
b6a5efc524 | ||
|
|
1e1220d0f9 | ||
|
|
0a691ffb4f | ||
|
|
746fb982a7 | ||
|
|
5cd467d5b0 | ||
|
|
6df91e35c8 | ||
|
|
837713f2b7 | ||
|
|
cd0222e208 | ||
|
|
7812e14898 | ||
|
|
c0f159a8a5 | ||
|
|
dd0c95f051 | ||
|
|
6d93bcf4ff | ||
|
|
05e9f9693a | ||
|
|
136947169b | ||
|
|
1379140cf6 | ||
|
|
2cb9228a7c | ||
|
|
b1652ddd97 | ||
|
|
c9f68e2466 | ||
|
|
70aabd8b11 | ||
|
|
0a69c7a08d | ||
|
|
e784ae21ea | ||
|
|
68a438f3d4 | ||
|
|
32fa49191e | ||
|
|
ce5372647a | ||
|
|
36b4190f23 | ||
|
|
ccc862f82a | ||
|
|
ff92f14a5b | ||
|
|
0b0baf2195 | ||
|
|
8ccec0ed9d | ||
|
|
46b42c8441 | ||
|
|
381f652c08 | ||
|
|
5c5e117da0 | ||
|
|
79b3b26ab2 | ||
|
|
89f8f047ae | ||
|
|
a23d44347e | ||
|
|
fb872a5b59 | ||
|
|
04d3f084e2 | ||
|
|
a5dfb499b3 | ||
|
|
5b1530b216 | ||
|
|
c6881c5e30 | ||
|
|
2f913666cd | ||
|
|
5ba3e3ce5b | ||
|
|
cb825106af | ||
|
|
39bc25b0b7 | ||
|
|
9e38cec769 | ||
|
|
099eadeafc | ||
|
|
e29c393629 | ||
|
|
0c6540e6d8 | ||
|
|
3c2cad43d0 | ||
|
|
de67c130fa | ||
|
|
55e62cdc79 | ||
|
|
eee41142b1 | ||
|
|
176a4a0962 | ||
|
|
32ca3d5dfe | ||
|
|
e87a10513d | ||
|
|
d2832b7169 | ||
|
|
daca2c7fff | ||
|
|
eb52f73cac | ||
|
|
5c4d08336c | ||
|
|
861b78ce8a | ||
|
|
1dc1923d7b | ||
|
|
7872336820 | ||
|
|
6e70f27bc6 | ||
|
|
ad5decc285 | ||
|
|
3e1c128600 | ||
|
|
d9817be8f3 | ||
|
|
e54dcd2859 | ||
|
|
5ec7815cfe | ||
|
|
adeee244e3 | ||
|
|
fe8a44b8c4 | ||
|
|
80ae9c3e82 | ||
|
|
88fbcd5f8c | ||
|
|
2b912b354d | ||
|
|
52eb2deb62 | ||
|
|
1812d1ba61 | ||
|
|
5f65be98df | ||
|
|
56c32d9691 | ||
|
|
36a933d0c4 | ||
|
|
d051bdf2c9 | ||
|
|
0424d214c5 | ||
|
|
c2aaa9b592 | ||
|
|
e450e52836 | ||
|
|
e2ecec03e8 | ||
|
|
318482d3ff | ||
|
|
2e4f665fa5 | ||
|
|
3d9738ac2f | ||
|
|
666cc855c1 | ||
|
|
3a059f6aca | ||
|
|
cdd3bc3cd6 | ||
|
|
87d57dab13 | ||
|
|
19789eb7ab | ||
|
|
395385f3e2 | ||
|
|
65aabc8333 | ||
|
|
5c16600b25 | ||
|
|
32fd4e33c8 | ||
|
|
560d247c9b | ||
|
|
40567fc8d0 | ||
|
|
9fc7bae13e | ||
|
|
515fd62dd8 | ||
|
|
170f901d86 | ||
|
|
d1fbe98379 | ||
|
|
fb1808d85a | ||
|
|
4094ca99dd | ||
|
|
fd05286e1a | ||
|
|
9436c83919 | ||
|
|
4f636d3d2c | ||
|
|
1a056be637 | ||
|
|
f38e184434 | ||
|
|
d24eb67fa2 | ||
|
|
c35e4f5750 | ||
|
|
0233f7b486 | ||
|
|
c129c38631 | ||
|
|
748ce8a23f | ||
|
|
a86166742f | ||
|
|
82c912237b | ||
|
|
5c89451985 | ||
|
|
2624b06729 | ||
|
|
7b7f5c09fd | ||
|
|
ad621e7208 | ||
|
|
034f18c419 | ||
|
|
7a6bf8b870 | ||
|
|
f529a5c64c | ||
|
|
69662f84df | ||
|
|
7d0ab1ba25 | ||
|
|
bd46e3e195 | ||
|
|
e5a92f64c0 | ||
|
|
798d0ab82b | ||
|
|
acaed1ef0e | ||
|
|
78f94e365c | ||
|
|
11379f150c | ||
|
|
41da55921c | ||
|
|
e9141ff5c9 | ||
|
|
2760de7951 | ||
|
|
203d6d3ac2 | ||
|
|
2a2192e196 | ||
|
|
876552b922 | ||
|
|
2b922508c5 | ||
|
|
cbee0542ad | ||
|
|
bc499bcfbf | ||
|
|
861c85f099 | ||
|
|
de63622cdd | ||
|
|
f5cf27a79e | ||
|
|
88e6cf7d8a | ||
|
|
0d28e663e4 | ||
|
|
716695e11e | ||
|
|
2770650340 | ||
|
|
0bff37b600 | ||
|
|
8614f11a31 | ||
|
|
b27319313d | ||
|
|
1f1c7826a4 | ||
|
|
d8736c17e6 | ||
|
|
00d72fe555 | ||
|
|
9943a94108 | ||
|
|
7a1b7b3291 | ||
|
|
832106dc86 | ||
|
|
24b9bd6ccc | ||
|
|
bd19b83db4 | ||
|
|
eb43f83c71 | ||
|
|
6e89197b3f | ||
|
|
51739a4dfe | ||
|
|
87f39b4273 | ||
|
|
fcea1ecbc2 | ||
|
|
7b9ebc3465 | ||
|
|
40ebaefac9 |
@@ -14,17 +14,13 @@ files:
|
|||||||
owner: root
|
owner: root
|
||||||
group: users
|
group: users
|
||||||
content: |
|
content: |
|
||||||
$(ls -td /opt/elasticbeanstalk/node-install/node-* | head -1)/bin/npm install -g npm@4
|
$(ls -td /opt/elasticbeanstalk/node-install/node-* | head -1)/bin/npm install -g npm@5
|
||||||
container_commands:
|
container_commands:
|
||||||
01_makeBabel:
|
01_makeBabel:
|
||||||
command: "touch /tmp/.babel.json"
|
command: "touch /tmp/.babel.json"
|
||||||
02_ownBabel:
|
02_ownBabel:
|
||||||
command: "chmod a+rw /tmp/.babel.json"
|
command: "chmod a+rw /tmp/.babel.json"
|
||||||
03_installBower:
|
03_installGulp:
|
||||||
command: "$NODE_HOME/bin/npm install -g bower"
|
|
||||||
04_installGulp:
|
|
||||||
command: "$NODE_HOME/bin/npm install -g gulp"
|
command: "$NODE_HOME/bin/npm install -g gulp"
|
||||||
05_runBower:
|
04_runGulp:
|
||||||
command: "$NODE_HOME/lib/node_modules/bower/bin/bower --config.interactive=false --allow-root install -f"
|
|
||||||
06_runGulp:
|
|
||||||
command: "$NODE_HOME/lib/node_modules/gulp/bin/gulp.js build"
|
command: "$NODE_HOME/lib/node_modules/gulp/bin/gulp.js build"
|
||||||
|
|||||||
@@ -6,6 +6,9 @@ website/transpiled-babel/
|
|||||||
website/common/transpiled-babel/
|
website/common/transpiled-babel/
|
||||||
dist/
|
dist/
|
||||||
dist-client/
|
dist-client/
|
||||||
|
apidoc_build/
|
||||||
|
content_cache/
|
||||||
|
node_modules/
|
||||||
|
|
||||||
# Not linted
|
# Not linted
|
||||||
website/client-old/
|
website/client-old/
|
||||||
@@ -16,5 +19,3 @@ migrations/*
|
|||||||
scripts/*
|
scripts/*
|
||||||
website/common/browserify.js
|
website/common/browserify.js
|
||||||
Gruntfile.js
|
Gruntfile.js
|
||||||
gulpfile.js
|
|
||||||
gulp
|
|
||||||
2
.github/CONTRIBUTING.md
vendored
2
.github/CONTRIBUTING.md
vendored
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
# Pull Request
|
# Pull Request
|
||||||
|
|
||||||
[Please see these instructions for adding a pull request](http://habitica.wikia.com/wiki/Using_Habitica_Git#Pull_Request)
|
[Please see these instructions for adding a pull request](http://habitica.wikia.com/wiki/Using_Your_Local_Install_to_Modify_Habitica%27s_Website_and_API)
|
||||||
|
|
||||||
# Requesting a feature
|
# Requesting a feature
|
||||||
|
|
||||||
|
|||||||
2
.github/ISSUE_TEMPLATE.md
vendored
2
.github/ISSUE_TEMPLATE.md
vendored
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
[//]: # (For more guidelines see https://github.com/HabitRPG/habitica/issues/2760)
|
[//]: # (For more guidelines see https://github.com/HabitRPG/habitica/issues/2760)
|
||||||
|
|
||||||
[//]: # (Fill out relevant information - UUID is found in Settings -> API)
|
[//]: # (Fill out relevant information - UUID is found from the Habitia website at User Icon > Settings > API)
|
||||||
### General Info
|
### General Info
|
||||||
* UUID:
|
* UUID:
|
||||||
* Browser:
|
* Browser:
|
||||||
|
|||||||
4
.github/PULL_REQUEST_TEMPLATE.md
vendored
4
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1,4 +1,4 @@
|
|||||||
[//]: # (Note: See http://habitica.wikia.com/wiki/Using_Habitica_Git#Pull_Request for more info)
|
[//]: # (Note: See http://habitica.wikia.com/wiki/Using_Your_Local_Install_to_Modify_Habitica%27s_Website_and_API for more info)
|
||||||
|
|
||||||
[//]: # (Put Issue # or URL here, if applicable. This will automatically close the issue if your PR is merged in)
|
[//]: # (Put Issue # or URL here, if applicable. This will automatically close the issue if your PR is merged in)
|
||||||
Fixes put_issue_url_here
|
Fixes put_issue_url_here
|
||||||
@@ -8,7 +8,7 @@ Fixes put_issue_url_here
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
[//]: # (Put User ID in here - found in Settings -> API)
|
[//]: # (Put User ID in here - found on the Habitica website at User Icon > Settings > API)
|
||||||
|
|
||||||
----
|
----
|
||||||
UUID:
|
UUID:
|
||||||
|
|||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -2,11 +2,14 @@
|
|||||||
website/client-old/gen
|
website/client-old/gen
|
||||||
website/client-old/common
|
website/client-old/common
|
||||||
website/client-old/apidoc
|
website/client-old/apidoc
|
||||||
|
website/build
|
||||||
website/client-old/js/habitrpg-shared.js*
|
website/client-old/js/habitrpg-shared.js*
|
||||||
website/client-old/css/habitrpg-shared.css
|
website/client-old/css/habitrpg-shared.css
|
||||||
website/transpiled-babel/
|
website/transpiled-babel/
|
||||||
website/common/transpiled-babel/
|
website/common/transpiled-babel/
|
||||||
node_modules
|
node_modules
|
||||||
|
content_cache
|
||||||
|
apidoc_build
|
||||||
*.swp
|
*.swp
|
||||||
.idea*
|
.idea*
|
||||||
config.json
|
config.json
|
||||||
|
|||||||
22
.travis.yml
22
.travis.yml
@@ -1,32 +1,22 @@
|
|||||||
language: node_js
|
language: node_js
|
||||||
node_js:
|
node_js:
|
||||||
- '6'
|
- '6'
|
||||||
sudo: required
|
|
||||||
dist: precise
|
|
||||||
services:
|
services:
|
||||||
- mongodb
|
- mongodb
|
||||||
addons:
|
cache:
|
||||||
apt:
|
directories:
|
||||||
sources:
|
- 'node_modules'
|
||||||
- ubuntu-toolchain-r-test
|
|
||||||
packages:
|
|
||||||
- g++-4.8
|
|
||||||
before_install:
|
before_install:
|
||||||
- $CXX --version
|
- npm install -g npm@5
|
||||||
- npm install -g npm@4
|
|
||||||
- if [ $REQUIRES_SERVER ]; then sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 7F0CEB10; echo 'deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen' | sudo tee /etc/apt/sources.list.d/mongodb.list; sudo apt-get update; sudo apt-get install mongodb-org-server; fi
|
|
||||||
install:
|
|
||||||
- npm install &> npm.install.log || (cat npm.install.log; false)
|
|
||||||
before_script:
|
before_script:
|
||||||
- npm run test:build
|
- npm run test:build
|
||||||
- cp config.json.example config.json
|
- cp config.json.example config.json
|
||||||
- sleep 15
|
- sleep 5
|
||||||
script:
|
script:
|
||||||
- npm run $TEST
|
- npm run $TEST
|
||||||
- if [ $COVERAGE ]; then ./node_modules/.bin/lcov-result-merger 'coverage/**/*.info' | ./node_modules/coveralls/bin/coveralls.js; fi
|
- if [ $COVERAGE ]; then ./node_modules/.bin/lcov-result-merger 'coverage/**/*.info' | ./node_modules/coveralls/bin/coveralls.js; fi
|
||||||
env:
|
env:
|
||||||
global:
|
global:
|
||||||
- CXX=g++-4.8
|
|
||||||
- DISABLE_REQUEST_LOGGING=true
|
- DISABLE_REQUEST_LOGGING=true
|
||||||
matrix:
|
matrix:
|
||||||
- TEST="lint"
|
- TEST="lint"
|
||||||
@@ -34,6 +24,4 @@ env:
|
|||||||
- TEST="test:sanity"
|
- TEST="test:sanity"
|
||||||
- TEST="test:content" COVERAGE=true
|
- TEST="test:content" COVERAGE=true
|
||||||
- TEST="test:common" COVERAGE=true
|
- TEST="test:common" COVERAGE=true
|
||||||
- TEST="test:karma" COVERAGE=true
|
|
||||||
- TEST="client:unit" COVERAGE=true
|
|
||||||
- TEST="apidoc"
|
- TEST="apidoc"
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
FROM node:boron
|
FROM node:boron
|
||||||
|
|
||||||
|
# Upgrade NPM to v5 (Yarn is needed because of this bug https://github.com/npm/npm/issues/16807)
|
||||||
|
# The used solution is suggested here https://github.com/npm/npm/issues/16807#issuecomment-313591975
|
||||||
|
RUN yarn global add npm@5
|
||||||
# Install global packages
|
# Install global packages
|
||||||
RUN npm install -g gulp grunt-cli bower mocha
|
RUN npm install -g gulp mocha
|
||||||
|
|
||||||
# Clone Habitica repo and install dependencies
|
# Clone Habitica repo and install dependencies
|
||||||
RUN mkdir -p /usr/src/habitrpg
|
RUN mkdir -p /usr/src/habitrpg
|
||||||
@@ -9,7 +12,6 @@ WORKDIR /usr/src/habitrpg
|
|||||||
RUN git clone https://github.com/HabitRPG/habitica.git /usr/src/habitrpg
|
RUN git clone https://github.com/HabitRPG/habitica.git /usr/src/habitrpg
|
||||||
RUN cp config.json.example config.json
|
RUN cp config.json.example config.json
|
||||||
RUN npm install
|
RUN npm install
|
||||||
RUN bower install --allow-root
|
|
||||||
|
|
||||||
# Create Build dir
|
# Create Build dir
|
||||||
RUN mkdir -p ./website/build
|
RUN mkdir -p ./website/build
|
||||||
|
|||||||
@@ -1,14 +1,27 @@
|
|||||||
FROM node:boron
|
FROM node:boron
|
||||||
|
|
||||||
|
ENV ADMIN_EMAIL admin@habitica.com
|
||||||
|
ENV AMAZON_PAYMENTS_CLIENT_ID amzn1.application-oa2-client.68ed9e6904ef438fbc1bf86bf494056e
|
||||||
|
ENV AMAZON_PAYMENTS_SELLER_ID AMQ3SB4SG5E91
|
||||||
|
ENV AMPLITUDE_KEY e8d4c24b3d6ef3ee73eeba715023dd43
|
||||||
|
ENV BASE_URL https://habitica.com
|
||||||
|
ENV FACEBOOK_KEY 128307497299777
|
||||||
|
ENV GA_ID UA-33510635-1
|
||||||
|
ENV GOOGLE_CLIENT_ID 1035232791481-32vtplgnjnd1aufv3mcu1lthf31795fq.apps.googleusercontent.com
|
||||||
|
ENV NODE_ENV production
|
||||||
|
ENV STRIPE_PUB_KEY pk_85fQ0yMECHNfHTSsZoxZXlPSwSNfA
|
||||||
|
|
||||||
|
# Upgrade NPM to v5 (Yarn is needed because of this bug https://github.com/npm/npm/issues/16807)
|
||||||
|
# The used solution is suggested here https://github.com/npm/npm/issues/16807#issuecomment-313591975
|
||||||
|
RUN yarn global add npm@5
|
||||||
# Install global packages
|
# Install global packages
|
||||||
RUN npm install -g gulp grunt-cli bower mocha
|
RUN npm install -g gulp mocha
|
||||||
|
|
||||||
# Clone Habitica repo and install dependencies
|
# Clone Habitica repo and install dependencies
|
||||||
RUN mkdir -p /usr/src/habitrpg
|
RUN mkdir -p /usr/src/habitrpg
|
||||||
WORKDIR /usr/src/habitrpg
|
WORKDIR /usr/src/habitrpg
|
||||||
RUN git clone --branch v3.111.6 https://github.com/HabitRPG/habitica.git /usr/src/habitrpg
|
RUN git clone --branch v4.23.2 https://github.com/HabitRPG/habitica.git /usr/src/habitrpg
|
||||||
RUN npm install
|
RUN npm install
|
||||||
RUN bower install --allow-root
|
|
||||||
RUN gulp build:prod --force
|
RUN gulp build:prod --force
|
||||||
|
|
||||||
# Create Build dir
|
# Create Build dir
|
||||||
|
|||||||
142
Gruntfile.js
142
Gruntfile.js
@@ -1,142 +0,0 @@
|
|||||||
/*global module:false*/
|
|
||||||
require('babel-register');
|
|
||||||
var _ = require('lodash');
|
|
||||||
module.exports = function(grunt) {
|
|
||||||
|
|
||||||
// Project configuration.
|
|
||||||
grunt.initConfig({
|
|
||||||
pkg: grunt.file.readJSON('package.json'),
|
|
||||||
|
|
||||||
karma: {
|
|
||||||
unit: {
|
|
||||||
configFile: 'test/client-old/spec/karma.conf.js'
|
|
||||||
},
|
|
||||||
continuous: {
|
|
||||||
configFile: 'test/client-old/spec/karma.conf.js',
|
|
||||||
singleRun: true,
|
|
||||||
autoWatch: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
clean: {
|
|
||||||
build: ['website/build']
|
|
||||||
},
|
|
||||||
|
|
||||||
cssmin: {
|
|
||||||
dist: {
|
|
||||||
options: {
|
|
||||||
report: 'gzip'
|
|
||||||
},
|
|
||||||
files:{
|
|
||||||
"website/client-old/css/habitrpg-shared.css": [
|
|
||||||
"website/assets/sprites/dist/spritesmith*.css",
|
|
||||||
"website/assets/sprites/css/backer.css",
|
|
||||||
"website/assets/sprites/css/Mounts.css",
|
|
||||||
"website/assets/sprites/css/index.css"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
stylus: {
|
|
||||||
build: {
|
|
||||||
options: {
|
|
||||||
compress: false, // AFTER
|
|
||||||
'include css': true,
|
|
||||||
paths: ['website/client-old']
|
|
||||||
},
|
|
||||||
files: {
|
|
||||||
'website/build/app.css': ['website/client-old/css/index.styl'],
|
|
||||||
'website/build/static.css': ['website/client-old/css/static.styl']
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
copy: {
|
|
||||||
build: {
|
|
||||||
files: [
|
|
||||||
{expand: true, cwd: 'website/client-old/', src: 'favicon.ico', dest: 'website/build/'},
|
|
||||||
{expand: true, cwd: 'website/client-old/', src: 'favicon_192x192.png', dest: 'website/build/'},
|
|
||||||
{expand: true, cwd: 'website/assets/sprites/dist/', src: 'spritesmith*.png', dest: 'website/build/static/sprites'},
|
|
||||||
{expand: true, cwd: 'website/assets/sprites/', src: 'backer-only/*.gif', dest: 'website/build/'},
|
|
||||||
{expand: true, cwd: 'website/assets/sprites/', src: 'npc_ian.gif', dest: 'website/build/'},
|
|
||||||
{expand: true, cwd: 'website/assets/sprites/', src: 'quest_*.gif', dest: 'website/build/'},
|
|
||||||
{expand: true, cwd: 'website/client-old/', src: 'bower_components/bootstrap/dist/fonts/*', dest: 'website/build/'}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// UPDATE IT WHEN YOU ADD SOME FILES NOT ALREADY MATCHED!
|
|
||||||
hashres: {
|
|
||||||
build: {
|
|
||||||
options: {
|
|
||||||
fileNameFormat: '${name}-${hash}.${ext}'
|
|
||||||
},
|
|
||||||
src: [
|
|
||||||
'website/build/*.js',
|
|
||||||
'website/build/*.css',
|
|
||||||
'website/build/favicon.ico',
|
|
||||||
'website/build/favicon_192x192.png',
|
|
||||||
'website/build/*.png',
|
|
||||||
'website/build/static/sprites/*.png',
|
|
||||||
'website/build/*.gif',
|
|
||||||
'website/build/bower_components/bootstrap/dist/fonts/*'
|
|
||||||
],
|
|
||||||
dest: 'website/build/*.css'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
//Load build files from client-old/manifest.json
|
|
||||||
grunt.registerTask('loadManifestFiles', 'Load all build files from client-old/manifest.json', function(){
|
|
||||||
var files = grunt.file.readJSON('./website/client-old/manifest.json');
|
|
||||||
var uglify = {};
|
|
||||||
var cssmin = {};
|
|
||||||
|
|
||||||
_.each(files, function(val, key){
|
|
||||||
|
|
||||||
var js = uglify['website/build/' + key + '.js'] = [];
|
|
||||||
|
|
||||||
_.each(files[key].js, function(val){
|
|
||||||
var path = "./";
|
|
||||||
if( val.indexOf('common/') == -1)
|
|
||||||
path = './website/client-old/';
|
|
||||||
js.push(path + val);
|
|
||||||
});
|
|
||||||
|
|
||||||
var css = cssmin['website/build/' + key + '.css'] = [];
|
|
||||||
|
|
||||||
_.each(files[key].css, function(val){
|
|
||||||
var path = "./";
|
|
||||||
if( val.indexOf('common/') == -1) {
|
|
||||||
path = (val == 'app.css' || val == 'static.css') ? './website/build/' : './website/client-old/';
|
|
||||||
}
|
|
||||||
css.push(path + val)
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
grunt.config.set('uglify.build.files', uglify);
|
|
||||||
grunt.config.set('uglify.build.options', {compress: false});
|
|
||||||
|
|
||||||
grunt.config.set('cssmin.build.files', cssmin);
|
|
||||||
// Rewrite urls to relative path
|
|
||||||
grunt.config.set('cssmin.build.options', {'target': 'website/client-old/css/whatever-css.css'});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Register tasks.
|
|
||||||
grunt.registerTask('build:prod', ['loadManifestFiles', 'clean:build', 'uglify', 'stylus', 'cssmin', 'copy:build', 'hashres']);
|
|
||||||
grunt.registerTask('build:dev', ['cssmin', 'stylus']);
|
|
||||||
grunt.registerTask('build:test', ['build:dev']);
|
|
||||||
|
|
||||||
// Load tasks
|
|
||||||
grunt.loadNpmTasks('grunt-contrib-uglify');
|
|
||||||
grunt.loadNpmTasks('grunt-contrib-clean');
|
|
||||||
grunt.loadNpmTasks('grunt-contrib-stylus');
|
|
||||||
grunt.loadNpmTasks('grunt-contrib-cssmin');
|
|
||||||
grunt.loadNpmTasks('grunt-contrib-copy');
|
|
||||||
grunt.loadNpmTasks('grunt-contrib-watch');
|
|
||||||
grunt.loadNpmTasks('grunt-hashres');
|
|
||||||
if (process.env.NODE_ENV !== 'production') grunt.loadNpmTasks('grunt-karma');
|
|
||||||
|
|
||||||
};
|
|
||||||
@@ -16,5 +16,7 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
|
|||||||
config.vm.hostname = "habitrpg"
|
config.vm.hostname = "habitrpg"
|
||||||
config.vm.network "forwarded_port", guest: 3000, host: 3000, auto_correct: true
|
config.vm.network "forwarded_port", guest: 3000, host: 3000, auto_correct: true
|
||||||
config.vm.usable_port_range = (3000..3050)
|
config.vm.usable_port_range = (3000..3050)
|
||||||
|
config.vm.network "forwarded_port", guest: 8080, host: 8080, auto_correct: true
|
||||||
|
config.vm.usable_port_range = (8080..8130)
|
||||||
config.vm.provision :shell, :path => "vagrant_scripts/vagrant.sh"
|
config.vm.provision :shell, :path => "vagrant_scripts/vagrant.sh"
|
||||||
end
|
end
|
||||||
|
|||||||
56
bower.json
56
bower.json
@@ -1,56 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "HabitRPG",
|
|
||||||
"version": "0.1.1",
|
|
||||||
"homepage": "https://github.com/lefnire/habitrpg",
|
|
||||||
"authors": [
|
|
||||||
"Tyler Renelle <tylerrenelle@gmail.com>"
|
|
||||||
],
|
|
||||||
"private": true,
|
|
||||||
"ignore": [
|
|
||||||
"**/.*",
|
|
||||||
"node_modules",
|
|
||||||
"website/client-old/bower_components",
|
|
||||||
"test",
|
|
||||||
"tests"
|
|
||||||
],
|
|
||||||
"dependencies": {
|
|
||||||
"Angular-At-Directive": "snicker/Angular-At-Directive#c27bae207aa06d1e",
|
|
||||||
"angular": "1.3.9",
|
|
||||||
"angular-bootstrap": "0.13.0",
|
|
||||||
"angular-filter": "0.5.1",
|
|
||||||
"angular-loading-bar": "0.6.0",
|
|
||||||
"angular-resource": "1.3.9",
|
|
||||||
"angular-sanitize": "1.3.9",
|
|
||||||
"angular-ui": "0.4.0",
|
|
||||||
"angular-ui-router": "0.2.13",
|
|
||||||
"angular-ui-select2": "angular-ui/ui-select2#afa6589a54cb72815f",
|
|
||||||
"angular-ui-utils": "0.1.0",
|
|
||||||
"bootstrap": "3.1.0",
|
|
||||||
"bootstrap-growl": "ifightcrime/bootstrap-growl#162daa41cd1155f",
|
|
||||||
"bootstrap-tour": "0.10.1",
|
|
||||||
"css-social-buttons": "samcollins/css-social-buttons#v1.1.1 ",
|
|
||||||
"github-buttons": "mdo/github-buttons#v3.0.0",
|
|
||||||
"hello": "1.14.1",
|
|
||||||
"jquery": "2.1.0",
|
|
||||||
"jquery-colorbox": "1.4.36",
|
|
||||||
"jquery-ui": "1.10.3",
|
|
||||||
"jquery.cookie": "1.4.0",
|
|
||||||
"js-emoji": "snicker/js-emoji#f25d8a303f",
|
|
||||||
"ngInfiniteScroll": "1.1.0",
|
|
||||||
"pnotify": "1.3.1",
|
|
||||||
"sticky": "1.0.3",
|
|
||||||
"swagger-ui": "wordnik/swagger-ui#v2.0.24",
|
|
||||||
"smart-app-banner": "78ef9c0679723b25be1a0ae04f7b4aef7cbced4f",
|
|
||||||
"habitica-markdown": "1.2.2",
|
|
||||||
"pusher-js-auth": "^2.0.0",
|
|
||||||
"pusher-websocket-iso": "pusher#^3.2.0",
|
|
||||||
"taggle": "^1.11.1"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"angular-mocks": "1.3.9"
|
|
||||||
},
|
|
||||||
"resolutions": {
|
|
||||||
"angular": "1.3.9",
|
|
||||||
"jquery": ">=1.9.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -22,6 +22,8 @@
|
|||||||
"CRON_SEMI_SAFE_MODE":"false",
|
"CRON_SEMI_SAFE_MODE":"false",
|
||||||
"MAINTENANCE_MODE": "false",
|
"MAINTENANCE_MODE": "false",
|
||||||
"SESSION_SECRET":"YOUR SECRET HERE",
|
"SESSION_SECRET":"YOUR SECRET HERE",
|
||||||
|
"SESSION_SECRET_KEY": "1234567891234567891234567891234567891234567891234567891234567891",
|
||||||
|
"SESSION_SECRET_IV": "12345678912345678912345678912345",
|
||||||
"ADMIN_EMAIL": "you@example.com",
|
"ADMIN_EMAIL": "you@example.com",
|
||||||
"SMTP_USER":"user@example.com",
|
"SMTP_USER":"user@example.com",
|
||||||
"SMTP_PASS":"password",
|
"SMTP_PASS":"password",
|
||||||
@@ -71,6 +73,7 @@
|
|||||||
},
|
},
|
||||||
"IAP_GOOGLE_KEYDIR": "/path/to/google/public/key/dir/",
|
"IAP_GOOGLE_KEYDIR": "/path/to/google/public/key/dir/",
|
||||||
"LOGGLY_TOKEN": "token",
|
"LOGGLY_TOKEN": "token",
|
||||||
|
"LOGGLY_CLIENT_TOKEN": "token",
|
||||||
"LOGGLY_ACCOUNT": "account",
|
"LOGGLY_ACCOUNT": "account",
|
||||||
"PUSH_CONFIGS": {
|
"PUSH_CONFIGS": {
|
||||||
"GCM_SERVER_API_KEY": "",
|
"GCM_SERVER_API_KEY": "",
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
web:
|
version: "3"
|
||||||
volumes:
|
services:
|
||||||
- '.:/usr/src/habitrpg'
|
|
||||||
|
client:
|
||||||
|
volumes:
|
||||||
|
- '.:/usr/src/habitrpg'
|
||||||
|
|
||||||
|
server:
|
||||||
|
volumes:
|
||||||
|
- '.:/usr/src/habitrpg'
|
||||||
|
|||||||
@@ -1,13 +1,36 @@
|
|||||||
web:
|
version: "3"
|
||||||
build: .
|
services:
|
||||||
ports:
|
|
||||||
- "3000:3000"
|
|
||||||
links:
|
|
||||||
- mongo
|
|
||||||
environment:
|
|
||||||
- NODE_DB_URI=mongodb://mongo/habitrpg
|
|
||||||
|
|
||||||
mongo:
|
client:
|
||||||
image: mongo
|
build: .
|
||||||
ports:
|
networks:
|
||||||
- "27017:27017"
|
- habitica
|
||||||
|
environment:
|
||||||
|
- BASE_URL=http://server:3000
|
||||||
|
ports:
|
||||||
|
- "8080:8080"
|
||||||
|
command: ["npm", "run", "client:dev"]
|
||||||
|
depends_on:
|
||||||
|
- server
|
||||||
|
|
||||||
|
server:
|
||||||
|
build: .
|
||||||
|
ports:
|
||||||
|
- "3000:3000"
|
||||||
|
networks:
|
||||||
|
- habitica
|
||||||
|
environment:
|
||||||
|
- NODE_DB_URI=mongodb://mongo/habitrpg
|
||||||
|
depends_on:
|
||||||
|
- mongo
|
||||||
|
|
||||||
|
mongo:
|
||||||
|
image: mongo
|
||||||
|
ports:
|
||||||
|
- "27017:27017"
|
||||||
|
networks:
|
||||||
|
- habitica
|
||||||
|
|
||||||
|
networks:
|
||||||
|
habitica:
|
||||||
|
driver: bridge
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
{
|
|
||||||
"root": true,
|
|
||||||
"env": {
|
|
||||||
"node": true,
|
|
||||||
},
|
|
||||||
"extends": [
|
|
||||||
"habitrpg/server",
|
|
||||||
"habitrpg/babel"
|
|
||||||
],
|
|
||||||
}
|
|
||||||
@@ -2,7 +2,7 @@ import gulp from 'gulp';
|
|||||||
import clean from 'rimraf';
|
import clean from 'rimraf';
|
||||||
import apidoc from 'apidoc';
|
import apidoc from 'apidoc';
|
||||||
|
|
||||||
const APIDOC_DEST_PATH = './website/build/apidoc';
|
const APIDOC_DEST_PATH = './apidoc_build';
|
||||||
const APIDOC_SRC_PATH = './website/server';
|
const APIDOC_SRC_PATH = './website/server';
|
||||||
gulp.task('apidoc:clean', (done) => {
|
gulp.task('apidoc:clean', (done) => {
|
||||||
clean(APIDOC_DEST_PATH, done);
|
clean(APIDOC_DEST_PATH, done);
|
||||||
@@ -22,5 +22,5 @@ gulp.task('apidoc', ['apidoc:clean'], (done) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
gulp.task('apidoc:watch', ['apidoc'], () => {
|
gulp.task('apidoc:watch', ['apidoc'], () => {
|
||||||
return gulp.watch(APIDOC_SRC_PATH + '/**/*.js', ['apidoc']);
|
return gulp.watch(`${APIDOC_SRC_PATH}/**/*.js`, ['apidoc']);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
import gulp from 'gulp';
|
|
||||||
import browserify from 'browserify';
|
|
||||||
import source from 'vinyl-source-stream';
|
|
||||||
import buffer from 'vinyl-buffer';
|
|
||||||
import uglify from 'gulp-uglify';
|
|
||||||
import sourcemaps from 'gulp-sourcemaps';
|
|
||||||
import babel from 'babelify';
|
|
||||||
|
|
||||||
gulp.task('browserify', function () {
|
|
||||||
let bundler = browserify({
|
|
||||||
entries: './website/common/browserify.js',
|
|
||||||
debug: true,
|
|
||||||
transform: [[babel, { compact: false }]],
|
|
||||||
});
|
|
||||||
|
|
||||||
return bundler.bundle()
|
|
||||||
.pipe(source('habitrpg-shared.js'))
|
|
||||||
.pipe(buffer())
|
|
||||||
.pipe(sourcemaps.init({loadMaps: true}))
|
|
||||||
.pipe(uglify())
|
|
||||||
.on('error', function (err) {
|
|
||||||
console.error(err);
|
|
||||||
this.emit('end');
|
|
||||||
})
|
|
||||||
.pipe(sourcemaps.write('./'))
|
|
||||||
.pipe(gulp.dest('./website/client-old/js/'));
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task('browserify:watch', () => {
|
|
||||||
gulp.watch('./website/common/script/**/*.js', ['browserify']);
|
|
||||||
});
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
import gulp from 'gulp';
|
|
||||||
import fs from 'fs';
|
|
||||||
|
|
||||||
// Copy Bootstrap 4 config variables from /website /node_modules so we can check
|
|
||||||
// them into Git
|
|
||||||
|
|
||||||
const BOOSTRAP_NEW_CONFIG_PATH = 'website/client/assets/scss/bootstrap_config.scss';
|
|
||||||
const BOOTSTRAP_ORIGINAL_CONFIG_PATH = 'node_modules/bootstrap/scss/_custom.scss';
|
|
||||||
|
|
||||||
// https://stackoverflow.com/a/14387791/969528
|
|
||||||
function copyFile(source, target, cb) {
|
|
||||||
let cbCalled = false;
|
|
||||||
|
|
||||||
function done(err) {
|
|
||||||
if (!cbCalled) {
|
|
||||||
cb(err);
|
|
||||||
cbCalled = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let rd = fs.createReadStream(source);
|
|
||||||
rd.on('error', done);
|
|
||||||
let wr = fs.createWriteStream(target);
|
|
||||||
wr.on('error', done);
|
|
||||||
wr.on('close', () => done());
|
|
||||||
rd.pipe(wr);
|
|
||||||
}
|
|
||||||
|
|
||||||
gulp.task('bootstrap', (done) => {
|
|
||||||
// use new config
|
|
||||||
copyFile(
|
|
||||||
BOOSTRAP_NEW_CONFIG_PATH,
|
|
||||||
BOOTSTRAP_ORIGINAL_CONFIG_PATH,
|
|
||||||
done,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
@@ -1,13 +1,10 @@
|
|||||||
import gulp from 'gulp';
|
import gulp from 'gulp';
|
||||||
import runSequence from 'run-sequence';
|
|
||||||
import babel from 'gulp-babel';
|
import babel from 'gulp-babel';
|
||||||
require('gulp-grunt')(gulp);
|
import webpackProductionBuild from '../webpack/build';
|
||||||
|
|
||||||
gulp.task('build', () => {
|
gulp.task('build', () => {
|
||||||
if (process.env.NODE_ENV === 'production') {
|
if (process.env.NODE_ENV === 'production') { // eslint-disable-line no-process-env
|
||||||
gulp.start('build:prod');
|
gulp.start('build:prod');
|
||||||
} else {
|
|
||||||
gulp.start('build:dev');
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -25,18 +22,16 @@ gulp.task('build:common', () => {
|
|||||||
|
|
||||||
gulp.task('build:server', ['build:src', 'build:common']);
|
gulp.task('build:server', ['build:src', 'build:common']);
|
||||||
|
|
||||||
gulp.task('build:dev', ['browserify', 'prepare:staticNewStuff'], (done) => {
|
// Client Production Build
|
||||||
gulp.start('grunt-build:dev', done);
|
gulp.task('build:client', (done) => {
|
||||||
|
webpackProductionBuild((err, output) => {
|
||||||
|
if (err) return done(err);
|
||||||
|
console.log(output); // eslint-disable-line no-console
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
gulp.task('build:dev:watch', ['build:dev'], () => {
|
gulp.task('build:prod', [
|
||||||
gulp.watch(['website/client-old/**/*.styl', 'website/common/script/*']);
|
'build:server',
|
||||||
});
|
'build:client',
|
||||||
|
'apidoc',
|
||||||
gulp.task('build:prod', ['browserify', 'build:server', 'prepare:staticNewStuff'], (done) => {
|
]);
|
||||||
runSequence(
|
|
||||||
'grunt-build:prod',
|
|
||||||
'apidoc',
|
|
||||||
done
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -7,10 +7,11 @@ import gulp from 'gulp';
|
|||||||
|
|
||||||
// Add additional properties to the repl's context
|
// Add additional properties to the repl's context
|
||||||
let improveRepl = (context) => {
|
let improveRepl = (context) => {
|
||||||
|
|
||||||
// Let "exit" and "quit" terminate the console
|
// Let "exit" and "quit" terminate the console
|
||||||
['exit', 'quit'].forEach((term) => {
|
['exit', 'quit'].forEach((term) => {
|
||||||
Object.defineProperty(context, term, { get () { process.exit(); }});
|
Object.defineProperty(context, term, { get () {
|
||||||
|
process.exit();
|
||||||
|
}});
|
||||||
});
|
});
|
||||||
|
|
||||||
// "clear" clears the screen
|
// "clear" clears the screen
|
||||||
@@ -18,12 +19,12 @@ let improveRepl = (context) => {
|
|||||||
process.stdout.write('\u001B[2J\u001B[0;0f');
|
process.stdout.write('\u001B[2J\u001B[0;0f');
|
||||||
}});
|
}});
|
||||||
|
|
||||||
context.Challenge = require('../website/server/models/challenge').model;
|
context.Challenge = require('../website/server/models/challenge').model; // eslint-disable-line global-require
|
||||||
context.Group = require('../website/server/models/group').model;
|
context.Group = require('../website/server/models/group').model; // eslint-disable-line global-require
|
||||||
context.User = require('../website/server/models/user').model;
|
context.User = require('../website/server/models/user').model; // eslint-disable-line global-require
|
||||||
|
|
||||||
var isProd = nconf.get('NODE_ENV') === 'production';
|
const isProd = nconf.get('NODE_ENV') === 'production';
|
||||||
var mongooseOptions = !isProd ? {} : {
|
const mongooseOptions = !isProd ? {} : {
|
||||||
replset: { socketOptions: { keepAlive: 1, connectTimeoutMS: 30000 } },
|
replset: { socketOptions: { keepAlive: 1, connectTimeoutMS: 30000 } },
|
||||||
server: { socketOptions: { keepAlive: 1, connectTimeoutMS: 30000 } },
|
server: { socketOptions: { keepAlive: 1, connectTimeoutMS: 30000 } },
|
||||||
};
|
};
|
||||||
@@ -31,16 +32,15 @@ let improveRepl = (context) => {
|
|||||||
mongoose.connect(
|
mongoose.connect(
|
||||||
nconf.get('NODE_DB_URI'),
|
nconf.get('NODE_DB_URI'),
|
||||||
mongooseOptions,
|
mongooseOptions,
|
||||||
function (err) {
|
(err) => {
|
||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
logger.info('Connected with Mongoose');
|
logger.info('Connected with Mongoose');
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
gulp.task('console', (cb) => {
|
gulp.task('console', () => {
|
||||||
improveRepl(repl.start({
|
improveRepl(repl.start({
|
||||||
prompt: 'Habitica > ',
|
prompt: 'Habitica > ',
|
||||||
}).context);
|
}).context);
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
import gulp from 'gulp';
|
|
||||||
import jade from 'jade';
|
|
||||||
import {writeFileSync} from 'fs';
|
|
||||||
|
|
||||||
gulp.task('prepare:staticNewStuff', () => {
|
|
||||||
writeFileSync(
|
|
||||||
'./website/client-old/new-stuff.html',
|
|
||||||
jade.compileFile('./website/views/shared/new-stuff.jade')()
|
|
||||||
);
|
|
||||||
});
|
|
||||||
@@ -10,82 +10,39 @@ import {each} from 'lodash';
|
|||||||
|
|
||||||
// https://github.com/Ensighten/grunt-spritesmith/issues/67#issuecomment-34786248
|
// https://github.com/Ensighten/grunt-spritesmith/issues/67#issuecomment-34786248
|
||||||
const MAX_SPRITESHEET_SIZE = 1024 * 1024 * 3;
|
const MAX_SPRITESHEET_SIZE = 1024 * 1024 * 3;
|
||||||
const DIST_PATH = 'website/assets/sprites/dist/';
|
|
||||||
|
|
||||||
const IMG_DIST_PATH_NEW_CLIENT = 'website/static/sprites/';
|
const IMG_DIST_PATH = 'website/client/assets/images/sprites/';
|
||||||
const CSS_DIST_PATH_NEW_CLIENT = 'website/client/assets/css/sprites/';
|
const CSS_DIST_PATH = 'website/client/assets/css/sprites/';
|
||||||
|
|
||||||
gulp.task('sprites:compile', ['sprites:clean', 'sprites:main', 'sprites:largeSprites', 'sprites:checkCompiledDimensions']);
|
function checkForSpecialTreatment (name) {
|
||||||
|
let regex = /^hair|skin|beard|mustach|shirt|flower|^headAccessory_special_\w+Ears|^eyewear_special_\w+TopFrame/;
|
||||||
|
return name.match(regex) || name === 'head_0';
|
||||||
|
}
|
||||||
|
|
||||||
gulp.task('sprites:main', () => {
|
function calculateImgDimensions (img, addPadding) {
|
||||||
let mainSrc = sync('website/assets/sprites/spritesmith/**/*.png');
|
let dims = sizeOf(img);
|
||||||
return createSpritesStream('main', mainSrc);
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task('sprites:largeSprites', () => {
|
let requiresSpecialTreatment = checkForSpecialTreatment(img);
|
||||||
let largeSrc = sync('website/assets/sprites/spritesmith_large/**/*.png');
|
if (requiresSpecialTreatment) {
|
||||||
return createSpritesStream('largeSprites', largeSrc);
|
let newWidth = dims.width < 90 ? 90 : dims.width;
|
||||||
});
|
let newHeight = dims.height < 90 ? 90 : dims.height;
|
||||||
|
dims = {
|
||||||
gulp.task('sprites:clean', (done) => {
|
width: newWidth,
|
||||||
clean(`{${DIST_PATH}spritesmith*,${IMG_DIST_PATH_NEW_CLIENT}spritesmith*,${CSS_DIST_PATH_NEW_CLIENT}spritesmith*}`, done);
|
height: newHeight,
|
||||||
});
|
};
|
||||||
|
|
||||||
gulp.task('sprites:checkCompiledDimensions', ['sprites:main', 'sprites:largeSprites'], () => {
|
|
||||||
console.log('Verifiying that images do not exceed max dimensions');
|
|
||||||
|
|
||||||
let numberOfSheetsThatAreTooBig = 0;
|
|
||||||
|
|
||||||
let distSpritesheets = sync(`${DIST_PATH}*.png`);
|
|
||||||
|
|
||||||
each(distSpritesheets, (img, index) => {
|
|
||||||
let spriteSize = calculateImgDimensions(img);
|
|
||||||
|
|
||||||
if (spriteSize > MAX_SPRITESHEET_SIZE) {
|
|
||||||
numberOfSheetsThatAreTooBig++;
|
|
||||||
let name = basename(img, '.png');
|
|
||||||
console.error(`WARNING: ${name} might be too big - ${spriteSize} > ${MAX_SPRITESHEET_SIZE}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (numberOfSheetsThatAreTooBig > 0) {
|
|
||||||
console.error(`${numberOfSheetsThatAreTooBig} sheets might too big for mobile Safari to be able to handle them, but there is a margin of error in these calculations so it is probably okay. Mention this to an admin so they can test a staging site on mobile Safari after your PR is merged.`); // https://github.com/HabitRPG/habitica/pull/6683#issuecomment-185462180
|
|
||||||
} else {
|
|
||||||
console.log('All images are within the correct dimensions');
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
function createSpritesStream (name, src) {
|
let padding = 0;
|
||||||
let spritesheetSliceIndicies = calculateSpritesheetsSrcIndicies(src);
|
|
||||||
let stream = mergeStream();
|
|
||||||
|
|
||||||
each(spritesheetSliceIndicies, (start, index) => {
|
if (addPadding) {
|
||||||
let slicedSrc = src.slice(start, spritesheetSliceIndicies[index + 1]);
|
padding = dims.width * 8 + dims.height * 8;
|
||||||
|
}
|
||||||
|
|
||||||
let spriteData = gulp.src(slicedSrc)
|
if (!dims.width || !dims.height) console.error('MISSING DIMENSIONS:', dims); // eslint-disable-line no-console
|
||||||
.pipe(spritesmith({
|
|
||||||
imgName: `spritesmith-${name}-${index}.png`,
|
|
||||||
cssName: `spritesmith-${name}-${index}.css`,
|
|
||||||
algorithm: 'binary-tree',
|
|
||||||
padding: 1,
|
|
||||||
cssTemplate: 'website/assets/sprites/css/css.template.handlebars',
|
|
||||||
cssVarMap: cssVarMap,
|
|
||||||
}));
|
|
||||||
|
|
||||||
let imgStream = spriteData.img
|
let totalPixelSize = dims.width * dims.height + padding;
|
||||||
.pipe(imagemin())
|
|
||||||
.pipe(gulp.dest(IMG_DIST_PATH_NEW_CLIENT))
|
|
||||||
.pipe(gulp.dest(DIST_PATH));
|
|
||||||
|
|
||||||
let cssStream = spriteData.css
|
return totalPixelSize;
|
||||||
.pipe(gulp.dest(CSS_DIST_PATH_NEW_CLIENT))
|
|
||||||
.pipe(gulp.dest(DIST_PATH));
|
|
||||||
|
|
||||||
stream.add(imgStream);
|
|
||||||
stream.add(cssStream);
|
|
||||||
});
|
|
||||||
|
|
||||||
return stream;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function calculateSpritesheetsSrcIndicies (src) {
|
function calculateSpritesheetsSrcIndicies (src) {
|
||||||
@@ -105,37 +62,6 @@ function calculateSpritesheetsSrcIndicies (src) {
|
|||||||
return slices;
|
return slices;
|
||||||
}
|
}
|
||||||
|
|
||||||
function calculateImgDimensions (img, addPadding) {
|
|
||||||
let dims = sizeOf(img);
|
|
||||||
|
|
||||||
let requiresSpecialTreatment = checkForSpecialTreatment(img);
|
|
||||||
if (requiresSpecialTreatment) {
|
|
||||||
let newWidth = dims.width < 90 ? 90 : dims.width;
|
|
||||||
let newHeight = dims.height < 90 ? 90 : dims.height;
|
|
||||||
dims = {
|
|
||||||
width: newWidth,
|
|
||||||
height: newHeight,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
let padding = 0;
|
|
||||||
|
|
||||||
if (addPadding) {
|
|
||||||
padding = (dims.width * 8) + (dims.height * 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!dims.width || !dims.height) console.error('MISSING DIMENSIONS:', dims);
|
|
||||||
|
|
||||||
let totalPixelSize = (dims.width * dims.height) + padding;
|
|
||||||
|
|
||||||
return totalPixelSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
function checkForSpecialTreatment (name) {
|
|
||||||
let regex = /^hair|skin|beard|mustach|shirt|flower|^headAccessory_special_\w+Ears|^eyewear_special_\w+TopFrame/;
|
|
||||||
return name.match(regex) || name === 'head_0';
|
|
||||||
}
|
|
||||||
|
|
||||||
function cssVarMap (sprite) {
|
function cssVarMap (sprite) {
|
||||||
// For hair, skins, beards, etc. we want to output a '.customize-options.WHATEVER' class, which works as a
|
// For hair, skins, beards, etc. we want to output a '.customize-options.WHATEVER' class, which works as a
|
||||||
// 60x60 image pointing at the proper part of the 90x90 sprite.
|
// 60x60 image pointing at the proper part of the 90x90 sprite.
|
||||||
@@ -144,18 +70,93 @@ function cssVarMap (sprite) {
|
|||||||
if (requiresSpecialTreatment) {
|
if (requiresSpecialTreatment) {
|
||||||
sprite.custom = {
|
sprite.custom = {
|
||||||
px: {
|
px: {
|
||||||
offset_x: `-${ sprite.x + 25 }px`,
|
offsetX: `-${ sprite.x + 25 }px`,
|
||||||
offset_y: `-${ sprite.y + 15 }px`,
|
offsetY: `-${ sprite.y + 15 }px`,
|
||||||
width: '60px',
|
width: '60px',
|
||||||
height: '60px',
|
height: '60px',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (~sprite.name.indexOf('shirt'))
|
if (sprite.name.indexOf('shirt') !== -1)
|
||||||
sprite.custom.px.offset_y = `-${ sprite.y + 30 }px`; // even more for shirts
|
sprite.custom.px.offsetY = `-${ sprite.y + 35 }px`; // even more for shirts
|
||||||
if (~sprite.name.indexOf('hair_base')) {
|
if (sprite.name.indexOf('hair_base') !== -1) {
|
||||||
let styleArray = sprite.name.split('_').slice(2,3);
|
let styleArray = sprite.name.split('_').slice(2, 3);
|
||||||
if (Number(styleArray[0]) > 14)
|
if (Number(styleArray[0]) > 14)
|
||||||
sprite.custom.px.offset_y = `-${ sprite.y }px`; // don't crop updos
|
sprite.custom.px.offsetY = `-${ sprite.y }px`; // don't crop updos
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createSpritesStream (name, src) {
|
||||||
|
let spritesheetSliceIndicies = calculateSpritesheetsSrcIndicies(src);
|
||||||
|
let stream = mergeStream();
|
||||||
|
|
||||||
|
each(spritesheetSliceIndicies, (start, index) => {
|
||||||
|
let slicedSrc = src.slice(start, spritesheetSliceIndicies[index + 1]);
|
||||||
|
|
||||||
|
let spriteData = gulp.src(slicedSrc)
|
||||||
|
.pipe(spritesmith({
|
||||||
|
imgName: `spritesmith-${name}-${index}.png`,
|
||||||
|
cssName: `spritesmith-${name}-${index}.css`,
|
||||||
|
algorithm: 'binary-tree',
|
||||||
|
padding: 1,
|
||||||
|
cssTemplate: 'website/raw_sprites/css/css.template.handlebars',
|
||||||
|
cssVarMap,
|
||||||
|
}));
|
||||||
|
|
||||||
|
let imgStream = spriteData.img
|
||||||
|
.pipe(imagemin())
|
||||||
|
.pipe(gulp.dest(IMG_DIST_PATH));
|
||||||
|
|
||||||
|
let cssStream = spriteData.css
|
||||||
|
.pipe(gulp.dest(CSS_DIST_PATH));
|
||||||
|
|
||||||
|
stream.add(imgStream);
|
||||||
|
stream.add(cssStream);
|
||||||
|
});
|
||||||
|
|
||||||
|
return stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
gulp.task('sprites:compile', ['sprites:clean', 'sprites:main', 'sprites:largeSprites', 'sprites:checkCompiledDimensions']);
|
||||||
|
|
||||||
|
gulp.task('sprites:main', () => {
|
||||||
|
let mainSrc = sync('website/raw_sprites/spritesmith/**/*.png');
|
||||||
|
return createSpritesStream('main', mainSrc);
|
||||||
|
});
|
||||||
|
|
||||||
|
gulp.task('sprites:largeSprites', () => {
|
||||||
|
let largeSrc = sync('website/raw_sprites/spritesmith_large/**/*.png');
|
||||||
|
return createSpritesStream('largeSprites', largeSrc);
|
||||||
|
});
|
||||||
|
|
||||||
|
gulp.task('sprites:clean', (done) => {
|
||||||
|
clean(`${IMG_DIST_PATH}spritesmith*,${CSS_DIST_PATH}spritesmith*}`, done);
|
||||||
|
});
|
||||||
|
|
||||||
|
gulp.task('sprites:checkCompiledDimensions', ['sprites:main', 'sprites:largeSprites'], () => {
|
||||||
|
console.log('Verifiying that images do not exceed max dimensions'); // eslint-disable-line no-console
|
||||||
|
|
||||||
|
let numberOfSheetsThatAreTooBig = 0;
|
||||||
|
|
||||||
|
let distSpritesheets = sync(`${IMG_DIST_PATH}*.png`);
|
||||||
|
|
||||||
|
each(distSpritesheets, (img) => {
|
||||||
|
let spriteSize = calculateImgDimensions(img);
|
||||||
|
|
||||||
|
if (spriteSize > MAX_SPRITESHEET_SIZE) {
|
||||||
|
numberOfSheetsThatAreTooBig++;
|
||||||
|
let name = basename(img, '.png');
|
||||||
|
console.error(`WARNING: ${name} might be too big - ${spriteSize} > ${MAX_SPRITESHEET_SIZE}`); // eslint-disable-line no-console
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (numberOfSheetsThatAreTooBig > 0) {
|
||||||
|
// https://github.com/HabitRPG/habitica/pull/6683#issuecomment-185462180
|
||||||
|
console.error( // eslint-disable-line no-console
|
||||||
|
`${numberOfSheetsThatAreTooBig} sheets might too big for mobile Safari to be able to handle
|
||||||
|
them, but there is a margin of error in these calculations so it is probably okay. Mention
|
||||||
|
this to an admin so they can test a staging site on mobile Safari after your PR is merged.`);
|
||||||
|
} else {
|
||||||
|
console.log('All images are within the correct dimensions'); // eslint-disable-line no-console
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|||||||
@@ -3,8 +3,6 @@ import nodemon from 'gulp-nodemon';
|
|||||||
|
|
||||||
let pkg = require('../package.json');
|
let pkg = require('../package.json');
|
||||||
|
|
||||||
gulp.task('run:dev', ['nodemon', 'build:dev:watch']);
|
|
||||||
|
|
||||||
gulp.task('nodemon', () => {
|
gulp.task('nodemon', () => {
|
||||||
nodemon({
|
nodemon({
|
||||||
script: pkg.main,
|
script: pkg.main,
|
||||||
|
|||||||
@@ -1,21 +1,12 @@
|
|||||||
import {
|
import {
|
||||||
pipe,
|
pipe,
|
||||||
awaitPort,
|
|
||||||
kill,
|
|
||||||
runMochaTests,
|
|
||||||
} from './taskHelper';
|
} from './taskHelper';
|
||||||
import { server as karma } from 'karma';
|
|
||||||
import mongoose from 'mongoose';
|
import mongoose from 'mongoose';
|
||||||
import { exec } from 'child_process';
|
import { exec } from 'child_process';
|
||||||
import psTree from 'ps-tree';
|
|
||||||
import gulp from 'gulp';
|
import gulp from 'gulp';
|
||||||
import Bluebird from 'bluebird';
|
|
||||||
import runSequence from 'run-sequence';
|
import runSequence from 'run-sequence';
|
||||||
import os from 'os';
|
import os from 'os';
|
||||||
import nconf from 'nconf';
|
import nconf from 'nconf';
|
||||||
import fs from 'fs';
|
|
||||||
|
|
||||||
const i18n = require('../website/server/libs/i18n');
|
|
||||||
|
|
||||||
// TODO rewrite
|
// TODO rewrite
|
||||||
|
|
||||||
@@ -24,25 +15,23 @@ let server;
|
|||||||
|
|
||||||
const TEST_DB_URI = nconf.get('TEST_DB_URI');
|
const TEST_DB_URI = nconf.get('TEST_DB_URI');
|
||||||
|
|
||||||
const API_V3_TEST_COMMAND = 'npm run test:api-v3';
|
|
||||||
const SANITY_TEST_COMMAND = 'npm run test:sanity';
|
const SANITY_TEST_COMMAND = 'npm run test:sanity';
|
||||||
const COMMON_TEST_COMMAND = 'npm run test:common';
|
const COMMON_TEST_COMMAND = 'npm run test:common';
|
||||||
const CONTENT_TEST_COMMAND = 'npm run test:content';
|
const CONTENT_TEST_COMMAND = 'npm run test:content';
|
||||||
const CONTENT_OPTIONS = {maxBuffer: 1024 * 500};
|
const CONTENT_OPTIONS = {maxBuffer: 1024 * 500};
|
||||||
const KARMA_TEST_COMMAND = 'npm run test:karma';
|
|
||||||
|
|
||||||
/* Helper methods for reporting test summary */
|
/* Helper methods for reporting test summary */
|
||||||
let testResults = [];
|
let testResults = [];
|
||||||
let testCount = (stdout, regexp) => {
|
let testCount = (stdout, regexp) => {
|
||||||
let match = stdout.match(regexp);
|
let match = stdout.match(regexp);
|
||||||
return parseInt(match && match[1] || 0);
|
return parseInt(match && match[1] || 0, 10);
|
||||||
};
|
};
|
||||||
|
|
||||||
let testBin = (string, additionalEnvVariables = '') => {
|
let testBin = (string, additionalEnvVariables = '') => {
|
||||||
if (os.platform() === 'win32') {
|
if (os.platform() === 'win32') {
|
||||||
if (additionalEnvVariables != '') {
|
if (additionalEnvVariables !== '') {
|
||||||
additionalEnvVariables = additionalEnvVariables.split(' ').join('&&set ');
|
additionalEnvVariables = additionalEnvVariables.split(' ').join('&&set ');
|
||||||
additionalEnvVariables = 'set ' + additionalEnvVariables + '&&';
|
additionalEnvVariables = `set ${additionalEnvVariables}&&`;
|
||||||
}
|
}
|
||||||
return `set NODE_ENV=test&&${additionalEnvVariables}${string}`;
|
return `set NODE_ENV=test&&${additionalEnvVariables}${string}`;
|
||||||
} else {
|
} else {
|
||||||
@@ -50,9 +39,9 @@ let testBin = (string, additionalEnvVariables = '') => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
gulp.task('test:nodemon', (done) => {
|
gulp.task('test:nodemon', () => {
|
||||||
process.env.PORT = TEST_SERVER_PORT;
|
process.env.PORT = TEST_SERVER_PORT; // eslint-disable-line no-process-env
|
||||||
process.env.NODE_DB_URI = TEST_DB_URI;
|
process.env.NODE_DB_URI = TEST_DB_URI; // eslint-disable-line no-process-env
|
||||||
|
|
||||||
runSequence('nodemon');
|
runSequence('nodemon');
|
||||||
});
|
});
|
||||||
@@ -69,37 +58,27 @@ gulp.task('test:prepare:mongo', (cb) => {
|
|||||||
gulp.task('test:prepare:server', ['test:prepare:mongo'], () => {
|
gulp.task('test:prepare:server', ['test:prepare:mongo'], () => {
|
||||||
if (!server) {
|
if (!server) {
|
||||||
server = exec(testBin('node ./website/server/index.js', `NODE_DB_URI=${TEST_DB_URI} PORT=${TEST_SERVER_PORT}`), (error, stdout, stderr) => {
|
server = exec(testBin('node ./website/server/index.js', `NODE_DB_URI=${TEST_DB_URI} PORT=${TEST_SERVER_PORT}`), (error, stdout, stderr) => {
|
||||||
if (error) { throw `Problem with the server: ${error}`; }
|
if (error) {
|
||||||
if (stderr) { console.error(stderr); }
|
throw new Error(`Problem with the server: ${error}`);
|
||||||
|
}
|
||||||
|
if (stderr) {
|
||||||
|
console.error(stderr); // eslint-disable-line no-console
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
gulp.task('test:prepare:translations', (cb) => {
|
gulp.task('test:prepare:build', ['build']);
|
||||||
fs.writeFile(
|
|
||||||
'test/client-old/spec/mocks/translations.js',
|
|
||||||
`if(!window.env) window.env = {};
|
|
||||||
window.env.translations = ${JSON.stringify(i18n.translations['en'])};`, cb);
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task('test:prepare:build', ['build', 'test:prepare:translations']);
|
|
||||||
// exec(testBin('grunt build:test'), cb);
|
|
||||||
|
|
||||||
gulp.task('test:prepare:webdriver', (cb) => {
|
|
||||||
exec('npm run test:prepare:webdriver', cb);
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task('test:prepare', [
|
gulp.task('test:prepare', [
|
||||||
'test:prepare:build',
|
'test:prepare:build',
|
||||||
'test:prepare:mongo',
|
'test:prepare:mongo',
|
||||||
'test:prepare:webdriver',
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
gulp.task('test:sanity', (cb) => {
|
gulp.task('test:sanity', (cb) => {
|
||||||
let runner = exec(
|
let runner = exec(
|
||||||
testBin(SANITY_TEST_COMMAND),
|
testBin(SANITY_TEST_COMMAND),
|
||||||
(err, stdout, stderr) => {
|
(err) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
@@ -112,7 +91,7 @@ gulp.task('test:sanity', (cb) => {
|
|||||||
gulp.task('test:common', ['test:prepare:build'], (cb) => {
|
gulp.task('test:common', ['test:prepare:build'], (cb) => {
|
||||||
let runner = exec(
|
let runner = exec(
|
||||||
testBin(COMMON_TEST_COMMAND),
|
testBin(COMMON_TEST_COMMAND),
|
||||||
(err, stdout, stderr) => {
|
(err) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
@@ -133,7 +112,7 @@ gulp.task('test:common:watch', ['test:common:clean'], () => {
|
|||||||
gulp.task('test:common:safe', ['test:prepare:build'], (cb) => {
|
gulp.task('test:common:safe', ['test:prepare:build'], (cb) => {
|
||||||
let runner = exec(
|
let runner = exec(
|
||||||
testBin(COMMON_TEST_COMMAND),
|
testBin(COMMON_TEST_COMMAND),
|
||||||
(err, stdout, stderr) => {
|
(err, stdout) => { // eslint-disable-line handle-callback-err
|
||||||
testResults.push({
|
testResults.push({
|
||||||
suite: 'Common Specs\t',
|
suite: 'Common Specs\t',
|
||||||
pass: testCount(stdout, /(\d+) passing/),
|
pass: testCount(stdout, /(\d+) passing/),
|
||||||
@@ -150,7 +129,7 @@ gulp.task('test:content', ['test:prepare:build'], (cb) => {
|
|||||||
let runner = exec(
|
let runner = exec(
|
||||||
testBin(CONTENT_TEST_COMMAND),
|
testBin(CONTENT_TEST_COMMAND),
|
||||||
CONTENT_OPTIONS,
|
CONTENT_OPTIONS,
|
||||||
(err, stdout, stderr) => {
|
(err) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
@@ -172,7 +151,7 @@ gulp.task('test:content:safe', ['test:prepare:build'], (cb) => {
|
|||||||
let runner = exec(
|
let runner = exec(
|
||||||
testBin(CONTENT_TEST_COMMAND),
|
testBin(CONTENT_TEST_COMMAND),
|
||||||
CONTENT_OPTIONS,
|
CONTENT_OPTIONS,
|
||||||
(err, stdout, stderr) => {
|
(err, stdout) => { // eslint-disable-line handle-callback-err
|
||||||
testResults.push({
|
testResults.push({
|
||||||
suite: 'Content Specs\t',
|
suite: 'Content Specs\t',
|
||||||
pass: testCount(stdout, /(\d+) passing/),
|
pass: testCount(stdout, /(\d+) passing/),
|
||||||
@@ -185,103 +164,10 @@ gulp.task('test:content:safe', ['test:prepare:build'], (cb) => {
|
|||||||
pipe(runner);
|
pipe(runner);
|
||||||
});
|
});
|
||||||
|
|
||||||
gulp.task('test:karma', ['test:prepare:build'], (cb) => {
|
|
||||||
let runner = exec(
|
|
||||||
testBin(KARMA_TEST_COMMAND),
|
|
||||||
(err, stdout) => {
|
|
||||||
if (err) {
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
cb();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
pipe(runner);
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task('test:karma:watch', ['test:prepare:build'], (cb) => {
|
|
||||||
let runner = exec(
|
|
||||||
testBin(`${KARMA_TEST_COMMAND}:watch`),
|
|
||||||
(err, stdout) => {
|
|
||||||
cb(err);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
pipe(runner);
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task('test:karma:safe', ['test:prepare:build'], (cb) => {
|
|
||||||
let runner = exec(
|
|
||||||
testBin(KARMA_TEST_COMMAND),
|
|
||||||
(err, stdout) => {
|
|
||||||
testResults.push({
|
|
||||||
suite: 'Karma Specs\t',
|
|
||||||
pass: testCount(stdout, /(\d+) tests? completed/),
|
|
||||||
fail: testCount(stdout, /(\d+) tests? failed/),
|
|
||||||
pend: testCount(stdout, /(\d+) tests? skipped/),
|
|
||||||
});
|
|
||||||
cb();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
pipe(runner);
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task('test:e2e', ['test:prepare', 'test:prepare:server'], (cb) => {
|
|
||||||
let support = [
|
|
||||||
'Xvfb :99 -screen 0 1024x768x24 -extension RANDR',
|
|
||||||
testBin('npm run test:e2e:webdriver', 'DISPLAY=:99'),
|
|
||||||
].map(exec);
|
|
||||||
support.push(server);
|
|
||||||
|
|
||||||
Bluebird.all([
|
|
||||||
awaitPort(TEST_SERVER_PORT),
|
|
||||||
awaitPort(4444),
|
|
||||||
]).then(() => {
|
|
||||||
let runner = exec(
|
|
||||||
'npm run test:e2e',
|
|
||||||
(err, stdout, stderr) => {
|
|
||||||
support.forEach(kill);
|
|
||||||
if (err) {
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
cb();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
pipe(runner);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task('test:e2e:safe', ['test:prepare', 'test:prepare:server'], (cb) => {
|
|
||||||
let support = [
|
|
||||||
'Xvfb :99 -screen 0 1024x768x24 -extension RANDR',
|
|
||||||
'npm run test:e2e:webdriver',
|
|
||||||
].map(exec);
|
|
||||||
|
|
||||||
Bluebird.all([
|
|
||||||
awaitPort(TEST_SERVER_PORT),
|
|
||||||
awaitPort(4444),
|
|
||||||
]).then(() => {
|
|
||||||
let runner = exec(
|
|
||||||
'npm run test:e2e',
|
|
||||||
(err, stdout, stderr) => {
|
|
||||||
let match = stdout.match(/(\d+) tests?.*(\d) failures?/);
|
|
||||||
|
|
||||||
testResults.push({
|
|
||||||
suite: 'End-to-End Specs\t',
|
|
||||||
pass: testCount(stdout, /(\d+) passing/),
|
|
||||||
fail: testCount(stdout, /(\d+) failing/),
|
|
||||||
pend: testCount(stdout, /(\d+) pending/),
|
|
||||||
});
|
|
||||||
support.forEach(kill);
|
|
||||||
cb();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
pipe(runner);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task('test:api-v3:unit', (done) => {
|
gulp.task('test:api-v3:unit', (done) => {
|
||||||
let runner = exec(
|
let runner = exec(
|
||||||
testBin('node_modules/.bin/istanbul cover --dir coverage/api-v3-unit --report lcovonly node_modules/mocha/bin/_mocha -- test/api/v3/unit --recursive --require ./test/helpers/start-server'),
|
testBin('node_modules/.bin/istanbul cover --dir coverage/api-v3-unit --report lcovonly node_modules/mocha/bin/_mocha -- test/api/v3/unit --recursive --require ./test/helpers/start-server'),
|
||||||
(err, stdout, stderr) => {
|
(err) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
@@ -300,7 +186,7 @@ gulp.task('test:api-v3:integration', (done) => {
|
|||||||
let runner = exec(
|
let runner = exec(
|
||||||
testBin('node_modules/.bin/istanbul cover --dir coverage/api-v3-integration --report lcovonly node_modules/mocha/bin/_mocha -- test/api/v3/integration --recursive --require ./test/helpers/start-server'),
|
testBin('node_modules/.bin/istanbul cover --dir coverage/api-v3-integration --report lcovonly node_modules/mocha/bin/_mocha -- test/api/v3/integration --recursive --require ./test/helpers/start-server'),
|
||||||
{maxBuffer: 500 * 1024},
|
{maxBuffer: 500 * 1024},
|
||||||
(err, stdout, stderr) => {
|
(err) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
@@ -320,7 +206,7 @@ gulp.task('test:api-v3:integration:separate-server', (done) => {
|
|||||||
let runner = exec(
|
let runner = exec(
|
||||||
testBin('mocha test/api/v3/integration --recursive --require ./test/helpers/start-server', 'LOAD_SERVER=0'),
|
testBin('mocha test/api/v3/integration --recursive --require ./test/helpers/start-server', 'LOAD_SERVER=0'),
|
||||||
{maxBuffer: 500 * 1024},
|
{maxBuffer: 500 * 1024},
|
||||||
(err, stdout, stderr) => done(err)
|
(err) => done(err)
|
||||||
);
|
);
|
||||||
|
|
||||||
pipe(runner);
|
pipe(runner);
|
||||||
@@ -331,7 +217,6 @@ gulp.task('test', (done) => {
|
|||||||
'test:sanity',
|
'test:sanity',
|
||||||
'test:content',
|
'test:content',
|
||||||
'test:common',
|
'test:common',
|
||||||
'test:karma',
|
|
||||||
'test:api-v3:unit',
|
'test:api-v3:unit',
|
||||||
'test:api-v3:integration',
|
'test:api-v3:integration',
|
||||||
done
|
done
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import nconf from 'nconf';
|
|
||||||
import gulp from 'gulp';
|
import gulp from 'gulp';
|
||||||
import { postToSlack, conf } from './taskHelper';
|
import { postToSlack, conf } from './taskHelper';
|
||||||
|
|
||||||
@@ -12,8 +11,82 @@ const SLACK_CONFIG = {
|
|||||||
|
|
||||||
const LOCALES = './website/common/locales/';
|
const LOCALES = './website/common/locales/';
|
||||||
const ENGLISH_LOCALE = `${LOCALES}en/`;
|
const ENGLISH_LOCALE = `${LOCALES}en/`;
|
||||||
|
|
||||||
|
|
||||||
|
function getArrayOfLanguages () {
|
||||||
|
let languages = fs.readdirSync(LOCALES);
|
||||||
|
languages.shift(); // Remove README.md from array of languages
|
||||||
|
|
||||||
|
return languages;
|
||||||
|
}
|
||||||
|
|
||||||
const ALL_LANGUAGES = getArrayOfLanguages();
|
const ALL_LANGUAGES = getArrayOfLanguages();
|
||||||
|
|
||||||
|
function stripOutNonJsonFiles (collection) {
|
||||||
|
let onlyJson = _.filter(collection, (file) => {
|
||||||
|
return file.match(/[a-zA-Z]*\.json/);
|
||||||
|
});
|
||||||
|
|
||||||
|
return onlyJson;
|
||||||
|
}
|
||||||
|
|
||||||
|
function eachTranslationFile (languages, cb) {
|
||||||
|
let jsonFiles = stripOutNonJsonFiles(fs.readdirSync(ENGLISH_LOCALE));
|
||||||
|
|
||||||
|
_.each(languages, (lang) => {
|
||||||
|
_.each(jsonFiles, (filename) => {
|
||||||
|
let parsedTranslationFile;
|
||||||
|
try {
|
||||||
|
const translationFile = fs.readFileSync(`${LOCALES}${lang}/${filename}`);
|
||||||
|
parsedTranslationFile = JSON.parse(translationFile);
|
||||||
|
} catch (err) {
|
||||||
|
return cb(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
let englishFile = fs.readFileSync(ENGLISH_LOCALE + filename);
|
||||||
|
let parsedEnglishFile = JSON.parse(englishFile);
|
||||||
|
|
||||||
|
cb(null, lang, filename, parsedEnglishFile, parsedTranslationFile);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function eachTranslationString (languages, cb) {
|
||||||
|
eachTranslationFile(languages, (error, language, filename, englishJSON, translationJSON) => {
|
||||||
|
if (error) return;
|
||||||
|
_.each(englishJSON, (string, key) => {
|
||||||
|
const translationString = translationJSON[key];
|
||||||
|
cb(language, filename, key, string, translationString);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatMessageForPosting (msg, items) {
|
||||||
|
let body = `*Warning:* ${msg}`;
|
||||||
|
body += '\n\n```\n';
|
||||||
|
body += items.join('\n');
|
||||||
|
body += '\n```';
|
||||||
|
|
||||||
|
return body;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getStringsWith (json, interpolationRegex) {
|
||||||
|
let strings = {};
|
||||||
|
|
||||||
|
_.each(json, (fileName) => {
|
||||||
|
const rawFile = fs.readFileSync(ENGLISH_LOCALE + fileName);
|
||||||
|
const parsedJson = JSON.parse(rawFile);
|
||||||
|
|
||||||
|
strings[fileName] = {};
|
||||||
|
_.each(parsedJson, (value, key) => {
|
||||||
|
const match = value.match(interpolationRegex);
|
||||||
|
if (match) strings[fileName][key] = match;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return strings;
|
||||||
|
}
|
||||||
|
|
||||||
const malformedStringExceptions = {
|
const malformedStringExceptions = {
|
||||||
messageDropFood: true,
|
messageDropFood: true,
|
||||||
armoireFood: true,
|
armoireFood: true,
|
||||||
@@ -23,7 +96,6 @@ const malformedStringExceptions = {
|
|||||||
gulp.task('transifex', ['transifex:missingFiles', 'transifex:missingStrings', 'transifex:malformedStrings']);
|
gulp.task('transifex', ['transifex:missingFiles', 'transifex:missingStrings', 'transifex:malformedStrings']);
|
||||||
|
|
||||||
gulp.task('transifex:missingFiles', () => {
|
gulp.task('transifex:missingFiles', () => {
|
||||||
|
|
||||||
let missingStrings = [];
|
let missingStrings = [];
|
||||||
|
|
||||||
eachTranslationFile(ALL_LANGUAGES, (error) => {
|
eachTranslationFile(ALL_LANGUAGES, (error) => {
|
||||||
@@ -40,7 +112,6 @@ gulp.task('transifex:missingFiles', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
gulp.task('transifex:missingStrings', () => {
|
gulp.task('transifex:missingStrings', () => {
|
||||||
|
|
||||||
let missingStrings = [];
|
let missingStrings = [];
|
||||||
|
|
||||||
eachTranslationString(ALL_LANGUAGES, (language, filename, key, englishString, translationString) => {
|
eachTranslationString(ALL_LANGUAGES, (language, filename, key, englishString, translationString) => {
|
||||||
@@ -58,7 +129,6 @@ gulp.task('transifex:missingStrings', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
gulp.task('transifex:malformedStrings', () => {
|
gulp.task('transifex:malformedStrings', () => {
|
||||||
|
|
||||||
let jsonFiles = stripOutNonJsonFiles(fs.readdirSync(ENGLISH_LOCALE));
|
let jsonFiles = stripOutNonJsonFiles(fs.readdirSync(ENGLISH_LOCALE));
|
||||||
let interpolationRegex = /<%= [a-zA-Z]* %>/g;
|
let interpolationRegex = /<%= [a-zA-Z]* %>/g;
|
||||||
let stringsToLookFor = getStringsWith(jsonFiles, interpolationRegex);
|
let stringsToLookFor = getStringsWith(jsonFiles, interpolationRegex);
|
||||||
@@ -66,25 +136,23 @@ gulp.task('transifex:malformedStrings', () => {
|
|||||||
let stringsWithMalformedInterpolations = [];
|
let stringsWithMalformedInterpolations = [];
|
||||||
let stringsWithIncorrectNumberOfInterpolations = [];
|
let stringsWithIncorrectNumberOfInterpolations = [];
|
||||||
|
|
||||||
let count = 0;
|
_.each(ALL_LANGUAGES, (lang) => {
|
||||||
_.each(ALL_LANGUAGES, function (lang) {
|
_.each(stringsToLookFor, (strings, filename) => {
|
||||||
|
let translationFile = fs.readFileSync(`${LOCALES}${lang}/${filename}`);
|
||||||
_.each(stringsToLookFor, function (strings, file) {
|
|
||||||
let translationFile = fs.readFileSync(LOCALES + lang + '/' + file);
|
|
||||||
let parsedTranslationFile = JSON.parse(translationFile);
|
let parsedTranslationFile = JSON.parse(translationFile);
|
||||||
|
|
||||||
_.each(strings, function (value, key) {
|
_.each(strings, (value, key) => { // eslint-disable-line max-nested-callbacks
|
||||||
let translationString = parsedTranslationFile[key];
|
let translationString = parsedTranslationFile[key];
|
||||||
if (!translationString) return;
|
if (!translationString) return;
|
||||||
|
|
||||||
let englishOccurences = stringsToLookFor[file][key];
|
let englishOccurences = stringsToLookFor[filename][key];
|
||||||
let translationOccurences = translationString.match(interpolationRegex);
|
let translationOccurences = translationString.match(interpolationRegex);
|
||||||
|
|
||||||
if (!translationOccurences) {
|
if (!translationOccurences) {
|
||||||
let malformedString = `${lang} - ${file} - ${key} - ${translationString}`;
|
let malformedString = `${lang} - ${filename} - ${key} - ${translationString}`;
|
||||||
stringsWithMalformedInterpolations.push(malformedString);
|
stringsWithMalformedInterpolations.push(malformedString);
|
||||||
} else if (englishOccurences.length !== translationOccurences.length && !malformedStringExceptions[key]) {
|
} else if (englishOccurences.length !== translationOccurences.length && !malformedStringExceptions[key]) {
|
||||||
let missingInterpolationString = `${lang} - ${file} - ${key} - ${translationString}`;
|
let missingInterpolationString = `${lang} - ${filename} - ${key} - ${translationString}`;
|
||||||
stringsWithIncorrectNumberOfInterpolations.push(missingInterpolationString);
|
stringsWithIncorrectNumberOfInterpolations.push(missingInterpolationString);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -103,74 +171,3 @@ gulp.task('transifex:malformedStrings', () => {
|
|||||||
postToSlack(formattedMessage, SLACK_CONFIG);
|
postToSlack(formattedMessage, SLACK_CONFIG);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function getArrayOfLanguages () {
|
|
||||||
let languages = fs.readdirSync(LOCALES);
|
|
||||||
languages.shift(); // Remove README.md from array of languages
|
|
||||||
|
|
||||||
return languages;
|
|
||||||
}
|
|
||||||
|
|
||||||
function eachTranslationFile (languages, cb) {
|
|
||||||
let jsonFiles = stripOutNonJsonFiles(fs.readdirSync(ENGLISH_LOCALE));
|
|
||||||
|
|
||||||
_.each(languages, (lang) => {
|
|
||||||
_.each(jsonFiles, (filename) => {
|
|
||||||
try {
|
|
||||||
var translationFile = fs.readFileSync(LOCALES + lang + '/' + filename);
|
|
||||||
var parsedTranslationFile = JSON.parse(translationFile);
|
|
||||||
} catch (err) {
|
|
||||||
return cb(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
let englishFile = fs.readFileSync(ENGLISH_LOCALE + filename);
|
|
||||||
let parsedEnglishFile = JSON.parse(englishFile);
|
|
||||||
|
|
||||||
cb(null, lang, filename, parsedEnglishFile, parsedTranslationFile);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function eachTranslationString (languages, cb) {
|
|
||||||
eachTranslationFile(languages, (error, language, filename, englishJSON, translationJSON) => {
|
|
||||||
if (error) return;
|
|
||||||
_.each(englishJSON, (string, key) => {
|
|
||||||
var translationString = translationJSON[key];
|
|
||||||
cb(language, filename, key, string, translationString);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatMessageForPosting (msg, items) {
|
|
||||||
let body = `*Warning:* ${msg}`;
|
|
||||||
body += '\n\n```\n';
|
|
||||||
body += items.join('\n');
|
|
||||||
body += '\n```';
|
|
||||||
|
|
||||||
return body;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getStringsWith (json, interpolationRegex) {
|
|
||||||
var strings = {};
|
|
||||||
|
|
||||||
_.each(json, function (file_name) {
|
|
||||||
var raw_file = fs.readFileSync(ENGLISH_LOCALE + file_name);
|
|
||||||
var parsed_json = JSON.parse(raw_file);
|
|
||||||
|
|
||||||
strings[file_name] = {};
|
|
||||||
_.each(parsed_json, function (value, key) {
|
|
||||||
var match = value.match(interpolationRegex);
|
|
||||||
if (match) strings[file_name][key] = match;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return strings;
|
|
||||||
}
|
|
||||||
|
|
||||||
function stripOutNonJsonFiles (collection) {
|
|
||||||
let onlyJson = _.filter(collection, (file) => {
|
|
||||||
return file.match(/[a-zA-Z]*\.json/);
|
|
||||||
});
|
|
||||||
|
|
||||||
return onlyJson;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { resolve } from 'path';
|
|||||||
* Get access to configruable values
|
* Get access to configruable values
|
||||||
*/
|
*/
|
||||||
nconf.argv().env().file({ file: 'config.json' });
|
nconf.argv().env().file({ file: 'config.json' });
|
||||||
export var conf = nconf;
|
export const conf = nconf;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Kill a child process and any sub-children that process may have spawned.
|
* Kill a child process and any sub-children that process may have spawned.
|
||||||
@@ -26,11 +26,12 @@ export function kill (proc) {
|
|||||||
pids.forEach(kill); return;
|
pids.forEach(kill); return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
exec(/^win/.test(process.platform)
|
exec(/^win/.test(process.platform) ?
|
||||||
? `taskkill /PID ${pid} /T /F`
|
`taskkill /PID ${pid} /T /F` :
|
||||||
: `kill -9 ${pid}`);
|
`kill -9 ${pid}`);
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e); // eslint-disable-line no-console
|
||||||
}
|
}
|
||||||
catch (e) { console.log(e); }
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -44,21 +45,25 @@ export function kill (proc) {
|
|||||||
* before failing.
|
* before failing.
|
||||||
*/
|
*/
|
||||||
export function awaitPort (port, max = 60) {
|
export function awaitPort (port, max = 60) {
|
||||||
return new Bluebird((reject, resolve) => {
|
return new Bluebird((rej, res) => {
|
||||||
let socket, timeout, interval;
|
let socket;
|
||||||
|
let timeout;
|
||||||
|
let interval;
|
||||||
|
|
||||||
timeout = setTimeout(() => {
|
timeout = setTimeout(() => {
|
||||||
clearInterval(interval);
|
clearInterval(interval);
|
||||||
reject(`Timed out after ${max} seconds`);
|
rej(`Timed out after ${max} seconds`);
|
||||||
}, max * 1000);
|
}, max * 1000);
|
||||||
|
|
||||||
interval = setInterval(() => {
|
interval = setInterval(() => {
|
||||||
socket = net.connect({port: port}, () => {
|
socket = net.connect({port}, () => {
|
||||||
clearInterval(interval);
|
clearInterval(interval);
|
||||||
clearTimeout(timeout);
|
clearTimeout(timeout);
|
||||||
socket.destroy();
|
socket.destroy();
|
||||||
resolve();
|
res();
|
||||||
}).on('error', () => { socket.destroy; });
|
}).on('error', () => {
|
||||||
|
socket.destroy();
|
||||||
|
});
|
||||||
}, 1000);
|
}, 1000);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -67,8 +72,12 @@ export function awaitPort (port, max = 60) {
|
|||||||
* Pipe the child's stdin and stderr to the parent process.
|
* Pipe the child's stdin and stderr to the parent process.
|
||||||
*/
|
*/
|
||||||
export function pipe (child) {
|
export function pipe (child) {
|
||||||
child.stdout.on('data', (data) => { process.stdout.write(data); });
|
child.stdout.on('data', (data) => {
|
||||||
child.stderr.on('data', (data) => { process.stderr.write(data); });
|
process.stdout.write(data);
|
||||||
|
});
|
||||||
|
child.stderr.on('data', (data) => {
|
||||||
|
process.stderr.write(data);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -78,8 +87,8 @@ export function postToSlack (msg, config = {}) {
|
|||||||
let slackUrl = nconf.get('SLACK_URL');
|
let slackUrl = nconf.get('SLACK_URL');
|
||||||
|
|
||||||
if (!slackUrl) {
|
if (!slackUrl) {
|
||||||
console.error('No slack post url specified. Your message was:');
|
console.error('No slack post url specified. Your message was:'); // eslint-disable-line no-console
|
||||||
console.log(msg);
|
console.log(msg); // eslint-disable-line no-console
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -89,15 +98,15 @@ export function postToSlack (msg, config = {}) {
|
|||||||
channel: `#${config.channel || '#general'}`,
|
channel: `#${config.channel || '#general'}`,
|
||||||
username: config.username || 'gulp task',
|
username: config.username || 'gulp task',
|
||||||
text: msg,
|
text: msg,
|
||||||
icon_emoji: `:${config.emoji || 'gulp'}:`,
|
icon_emoji: `:${config.emoji || 'gulp'}:`, // eslint-disable-line camelcase
|
||||||
})
|
})
|
||||||
.end((err, res) => {
|
.end((err) => {
|
||||||
if (err) console.error('Unable to post to slack', err);
|
if (err) console.error('Unable to post to slack', err); // eslint-disable-line no-console
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function runMochaTests (files, server, cb) {
|
export function runMochaTests (files, server, cb) {
|
||||||
require('../test/helpers/globals.helper');
|
require('../test/helpers/globals.helper'); // eslint-disable-line global-require
|
||||||
|
|
||||||
let mocha = new Mocha({reporter: 'spec'});
|
let mocha = new Mocha({reporter: 'spec'});
|
||||||
let tests = glob(files);
|
let tests = glob(files);
|
||||||
@@ -108,7 +117,7 @@ export function runMochaTests (files, server, cb) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
mocha.run((numberOfFailures) => {
|
mocha.run((numberOfFailures) => {
|
||||||
if (!process.env.RUN_INTEGRATION_TEST_FOREVER) {
|
if (!process.env.RUN_INTEGRATION_TEST_FOREVER) { // eslint-disable-line no-process-env
|
||||||
if (server) kill(server);
|
if (server) kill(server);
|
||||||
process.exit(numberOfFailures);
|
process.exit(numberOfFailures);
|
||||||
}
|
}
|
||||||
|
|||||||
13
gulpfile.js
13
gulpfile.js
@@ -8,13 +8,10 @@
|
|||||||
|
|
||||||
require('babel-register');
|
require('babel-register');
|
||||||
|
|
||||||
if (process.env.NODE_ENV === 'production') {
|
if (process.env.NODE_ENV === 'production') { // eslint-disable-line no-process-env
|
||||||
require('./gulp/gulp-apidoc');
|
require('./gulp/gulp-apidoc'); // eslint-disable-line global-require
|
||||||
require('./gulp/gulp-newstuff');
|
require('./gulp/gulp-build'); // eslint-disable-line global-require
|
||||||
require('./gulp/gulp-build');
|
|
||||||
require('./gulp/gulp-babelify');
|
|
||||||
require('./gulp/gulp-bootstrap');
|
|
||||||
} else {
|
} else {
|
||||||
require('glob').sync('./gulp/gulp-*').forEach(require);
|
require('glob').sync('./gulp/gulp-*').forEach(require); // eslint-disable-line global-require
|
||||||
require('gulp').task('default', ['test']);
|
require('gulp').task('default', ['test']); // eslint-disable-line global-require
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ var migrationName = '20140831_increase_gems_for_previous_contributions';
|
|||||||
* https://github.com/HabitRPG/habitrpg/issues/3933
|
* https://github.com/HabitRPG/habitrpg/issues/3933
|
||||||
* Increase Number of Gems for Contributors
|
* Increase Number of Gems for Contributors
|
||||||
* author: Alys (d904bd62-da08-416b-a816-ba797c9ee265)
|
* author: Alys (d904bd62-da08-416b-a816-ba797c9ee265)
|
||||||
*
|
*
|
||||||
* Increase everyone's gems per their contribution level.
|
* Increase everyone's gems per their contribution level.
|
||||||
* Originally they were given 2 gems per tier.
|
* Originally they were given 2 gems per tier.
|
||||||
* Now they are given 3 gems per tier for tiers 1,2,3
|
* Now they are given 3 gems per tier for tiers 1,2,3
|
||||||
@@ -70,7 +70,7 @@ dbUsers.findEach(query, fields, function(err, user) {
|
|||||||
var extraGems = tier; // tiers 1,2,3
|
var extraGems = tier; // tiers 1,2,3
|
||||||
if (tier > 3) { extraGems = 3 + (tier - 3) * 2; }
|
if (tier > 3) { extraGems = 3 + (tier - 3) * 2; }
|
||||||
if (tier == 8) { extraGems = 11; }
|
if (tier == 8) { extraGems = 11; }
|
||||||
extraBalance = extraGems / 4;
|
var extraBalance = extraGems / 4;
|
||||||
set['balance'] = user.balance + extraBalance;
|
set['balance'] = user.balance + extraBalance;
|
||||||
|
|
||||||
// Capture current state of user:
|
// Capture current state of user:
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ function findUsers(gt){
|
|||||||
console.log('User: ', countUsers, user._id);
|
console.log('User: ', countUsers, user._id);
|
||||||
|
|
||||||
var update = {
|
var update = {
|
||||||
$set: {};
|
$set: {}
|
||||||
};
|
};
|
||||||
|
|
||||||
if(user.auth && user.auth.local) {
|
if(user.auth && user.auth.local) {
|
||||||
@@ -60,4 +60,4 @@ function findUsers(gt){
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
findUsers();
|
findUsers();
|
||||||
|
|||||||
97
migrations/20170928_redesign_guilds.js
Normal file
97
migrations/20170928_redesign_guilds.js
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
var migrationName = '20170928_redesign_guilds.js';
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copy Guild Leader messages to end of Guild descriptions
|
||||||
|
* Copy Guild logos to beginning of Guild descriptions
|
||||||
|
*/
|
||||||
|
|
||||||
|
var monk = require('monk');
|
||||||
|
var connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
|
||||||
|
var dbGroups = monk(connectionString).get('groups', { castIds: false });
|
||||||
|
|
||||||
|
function processGroups(lastId) {
|
||||||
|
// specify a query to limit the affected groups (empty for all groups):
|
||||||
|
var query = {
|
||||||
|
};
|
||||||
|
|
||||||
|
var fields = {
|
||||||
|
'description': 1,
|
||||||
|
'logo': 1,
|
||||||
|
'leaderMessage': 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastId) {
|
||||||
|
query._id = {
|
||||||
|
$gt: lastId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dbGroups.find(query, {
|
||||||
|
fields: fields,
|
||||||
|
sort: {_id: 1},
|
||||||
|
limit: 250,
|
||||||
|
})
|
||||||
|
.then(updateGroups)
|
||||||
|
.catch(function (err) {
|
||||||
|
console.log(err);
|
||||||
|
return exiting(1, 'ERROR! ' + err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var progressCount = 1000;
|
||||||
|
var count = 0;
|
||||||
|
|
||||||
|
function updateGroups (groups) {
|
||||||
|
if (!groups || groups.length === 0) {
|
||||||
|
console.warn('All appropriate groups found and modified.');
|
||||||
|
displayData();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var groupPromises = groups.map(updateGroup);
|
||||||
|
var lastGroup = groups[groups.length - 1];
|
||||||
|
|
||||||
|
return Promise.all(groupPromises)
|
||||||
|
.then(function () {
|
||||||
|
processGroups(lastGroup._id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateGroup (group) {
|
||||||
|
count++;
|
||||||
|
|
||||||
|
var description = group.description;
|
||||||
|
|
||||||
|
if (group.logo) {
|
||||||
|
description = '\n\n \n\n' + description;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (group.leaderMessage) {
|
||||||
|
description = description + '\n\n \n\n' + group.leaderMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
var set = {
|
||||||
|
description: description,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (count % progressCount == 0) console.warn(count + ' ' + group._id);
|
||||||
|
|
||||||
|
return dbGroups.update({_id: group._id}, {$set:set});
|
||||||
|
}
|
||||||
|
|
||||||
|
function displayData() {
|
||||||
|
console.warn('\n' + count + ' groups processed\n');
|
||||||
|
return exiting(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function exiting(code, msg) {
|
||||||
|
code = code || 0; // 0 = success
|
||||||
|
if (code && !msg) { msg = 'ERROR!'; }
|
||||||
|
if (msg) {
|
||||||
|
if (code) { console.error(msg); }
|
||||||
|
else { console.log( msg); }
|
||||||
|
}
|
||||||
|
process.exit(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = processGroups;
|
||||||
128
migrations/20170928_redesign_launch.js
Normal file
128
migrations/20170928_redesign_launch.js
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
import { selectGearToPin } from '../website/common/script/ops/pinnedGearUtils';
|
||||||
|
|
||||||
|
var getItemInfo = require('../website/common/script/libs/getItemInfo');
|
||||||
|
|
||||||
|
var migrationName = '20170928_redesign_launch.js';
|
||||||
|
var authorName = 'paglias'; // in case script author needs to know when their ...
|
||||||
|
var authorUuid = 'ed4c688c-6652-4a92-9d03-a5a79844174a'; //... own data is done
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Migrate existing in app rewards lists to pinned items
|
||||||
|
* Award Veteran Pets
|
||||||
|
*/
|
||||||
|
|
||||||
|
var monk = require('monk');
|
||||||
|
var connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
|
||||||
|
var dbUsers = monk(connectionString).get('users', { castIds: false });
|
||||||
|
|
||||||
|
function processUsers(lastId) {
|
||||||
|
// specify a query to limit the affected users (empty for all users):
|
||||||
|
var query = {
|
||||||
|
'migration': {$ne:migrationName},
|
||||||
|
'auth.timestamps.loggedin': {$gt: new Date('2017-09-21')},
|
||||||
|
};
|
||||||
|
|
||||||
|
var fields = {
|
||||||
|
'items.pets': 1,
|
||||||
|
'items.gear': 1,
|
||||||
|
'stats.class': 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastId) {
|
||||||
|
query._id = {
|
||||||
|
$gt: lastId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dbUsers.find(query, {
|
||||||
|
fields: fields,
|
||||||
|
sort: {_id: 1},
|
||||||
|
limit: 250,
|
||||||
|
})
|
||||||
|
.then(updateUsers)
|
||||||
|
.catch(function (err) {
|
||||||
|
console.log(err);
|
||||||
|
return exiting(1, 'ERROR! ' + err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var progressCount = 1000;
|
||||||
|
var count = 0;
|
||||||
|
|
||||||
|
function updateUsers (users) {
|
||||||
|
if (!users || users.length === 0) {
|
||||||
|
console.warn('All appropriate users found and modified.');
|
||||||
|
displayData();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var userPromises = users.map(updateUser);
|
||||||
|
var lastUser = users[users.length - 1];
|
||||||
|
|
||||||
|
return Promise.all(userPromises)
|
||||||
|
.then(function () {
|
||||||
|
processUsers(lastUser._id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateUser (user) {
|
||||||
|
count++;
|
||||||
|
|
||||||
|
var set = {'migration': migrationName};
|
||||||
|
|
||||||
|
var oldRewardsList = selectGearToPin(user);
|
||||||
|
var newPinnedItems = [
|
||||||
|
{
|
||||||
|
type: 'armoire',
|
||||||
|
path: 'armoire',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'potion',
|
||||||
|
path: 'potion',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
oldRewardsList.forEach(item => {
|
||||||
|
var type = 'marketGear';
|
||||||
|
|
||||||
|
var itemInfo = getItemInfo(user, 'marketGear', item);
|
||||||
|
newPinnedItems.push({
|
||||||
|
type,
|
||||||
|
path: itemInfo.path,
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
set.pinnedItems = newPinnedItems;
|
||||||
|
|
||||||
|
if (user.items.pets['Lion-Veteran']) {
|
||||||
|
set['items.pets.Bear-Veteran'] = 5;
|
||||||
|
} else if (user.items.pets['Tiger-Veteran']) {
|
||||||
|
set['items.pets.Lion-Veteran'] = 5;
|
||||||
|
} else if (user.items.pets['Wolf-Veteran']) {
|
||||||
|
set['items.pets.Tiger-Veteran'] = 5;
|
||||||
|
} else {
|
||||||
|
set['items.pets.Wolf-Veteran'] = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count % progressCount == 0) console.warn(count + ' ' + user._id);
|
||||||
|
if (user._id == authorUuid) console.warn(authorName + ' processed');
|
||||||
|
|
||||||
|
return dbUsers.update({_id: user._id}, {$set:set});
|
||||||
|
}
|
||||||
|
|
||||||
|
function displayData() {
|
||||||
|
console.warn('\n' + count + ' users processed\n');
|
||||||
|
return exiting(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function exiting(code, msg) {
|
||||||
|
code = code || 0; // 0 = success
|
||||||
|
if (code && !msg) { msg = 'ERROR!'; }
|
||||||
|
if (msg) {
|
||||||
|
if (code) { console.error(msg); }
|
||||||
|
else { console.log( msg); }
|
||||||
|
}
|
||||||
|
process.exit(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = processUsers;
|
||||||
111
migrations/20171030_jackolanterns.js
Normal file
111
migrations/20171030_jackolanterns.js
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
var migrationName = '20171030_jackolanterns.js';
|
||||||
|
var authorName = 'Sabe'; // in case script author needs to know when their ...
|
||||||
|
var authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; //... own data is done
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Award the Jack-O'-Lantern ladder:
|
||||||
|
* Ghost Jack-O-Lantern Mount to owners of Ghost Jack-O-Lantern Pet
|
||||||
|
* Ghost Jack-O-Lantern Pet to owners of Jack-O-Lantern Mount
|
||||||
|
* Jack-O-Lantern Mount to owners of Jack-O-Lantern Pet
|
||||||
|
* Jack-O-Lantern Pet to everyone else
|
||||||
|
*/
|
||||||
|
|
||||||
|
var monk = require('monk');
|
||||||
|
var connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
|
||||||
|
var dbUsers = monk(connectionString).get('users', { castIds: false });
|
||||||
|
|
||||||
|
function processUsers(lastId) {
|
||||||
|
// specify a query to limit the affected users (empty for all users):
|
||||||
|
var query = {
|
||||||
|
'migration':{$ne:migrationName},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (lastId) {
|
||||||
|
query._id = {
|
||||||
|
$gt: lastId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dbUsers.find(query, {
|
||||||
|
sort: {_id: 1},
|
||||||
|
limit: 250,
|
||||||
|
fields: [
|
||||||
|
'items.pets',
|
||||||
|
'items.mounts',
|
||||||
|
] // specify fields we are interested in to limit retrieved data (empty if we're not reading data):
|
||||||
|
})
|
||||||
|
.then(updateUsers)
|
||||||
|
.catch(function (err) {
|
||||||
|
console.log(err);
|
||||||
|
return exiting(1, 'ERROR! ' + err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var progressCount = 1000;
|
||||||
|
var count = 0;
|
||||||
|
|
||||||
|
function updateUsers (users) {
|
||||||
|
if (!users || users.length === 0) {
|
||||||
|
console.warn('All appropriate users found and modified.');
|
||||||
|
displayData();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var userPromises = users.map(updateUser);
|
||||||
|
var lastUser = users[users.length - 1];
|
||||||
|
|
||||||
|
return Promise.all(userPromises)
|
||||||
|
.then(function () {
|
||||||
|
processUsers(lastUser._id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateUser (user) {
|
||||||
|
count++;
|
||||||
|
|
||||||
|
var set = {};
|
||||||
|
var inc = {
|
||||||
|
'items.food.Candy_Skeleton': 1,
|
||||||
|
'items.food.Candy_Base': 1,
|
||||||
|
'items.food.Candy_CottonCandyBlue': 1,
|
||||||
|
'items.food.Candy_CottonCandyPink': 1,
|
||||||
|
'items.food.Candy_Shade': 1,
|
||||||
|
'items.food.Candy_White': 1,
|
||||||
|
'items.food.Candy_Golden': 1,
|
||||||
|
'items.food.Candy_Zombie': 1,
|
||||||
|
'items.food.Candy_Desert': 1,
|
||||||
|
'items.food.Candy_Red': 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (user && user.items && user.items.pets && user.items.pets['JackOLantern-Ghost']) {
|
||||||
|
set = {'migration':migrationName, 'items.mounts.JackOLantern-Ghost': true};
|
||||||
|
} else if (user && user.items && user.items.mounts && user.items.mounts['JackOLantern-Base']) {
|
||||||
|
set = {'migration':migrationName, 'items.pets.JackOLantern-Ghost': 5};
|
||||||
|
} else if (user && user.items && user.items.pets && user.items.pets['JackOLantern-Base']) {
|
||||||
|
set = {'migration':migrationName, 'items.mounts.JackOLantern-Base': true};
|
||||||
|
} else {
|
||||||
|
set = {'migration':migrationName, 'items.pets.JackOLantern-Base': 5};
|
||||||
|
}
|
||||||
|
|
||||||
|
dbUsers.update({_id: user._id}, {$set:set, $inc:inc});
|
||||||
|
|
||||||
|
if (count % progressCount == 0) console.warn(count + ' ' + user._id);
|
||||||
|
if (user._id == authorUuid) console.warn(authorName + ' processed');
|
||||||
|
}
|
||||||
|
|
||||||
|
function displayData() {
|
||||||
|
console.warn('\n' + count + ' users processed\n');
|
||||||
|
return exiting(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function exiting(code, msg) {
|
||||||
|
code = code || 0; // 0 = success
|
||||||
|
if (code && !msg) { msg = 'ERROR!'; }
|
||||||
|
if (msg) {
|
||||||
|
if (code) { console.error(msg); }
|
||||||
|
else { console.log( msg); }
|
||||||
|
}
|
||||||
|
process.exit(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = processUsers;
|
||||||
128
migrations/20171117_turkey_ladder.js
Normal file
128
migrations/20171117_turkey_ladder.js
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
var migrationName = '20171117_turkey_ladder.js';
|
||||||
|
var authorName = 'Sabe'; // in case script author needs to know when their ...
|
||||||
|
var authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; //... own data is done
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Award the Turkey Day ladder:
|
||||||
|
* Grant Turkey Costume to those who have the Gilded Turkey mount
|
||||||
|
* Grant Gilded Turkey mount to those who have the Gilded Turkey pet
|
||||||
|
* Grant Gilded Turkey pet to those who have the Base Turkey mount
|
||||||
|
* Grant Base Turkey mount to those who have the Base Turkey pet
|
||||||
|
* Grant Base Turkey pet to those who have none of the above yet
|
||||||
|
*/
|
||||||
|
|
||||||
|
var monk = require('monk');
|
||||||
|
var connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
|
||||||
|
var dbUsers = monk(connectionString).get('users', { castIds: false });
|
||||||
|
|
||||||
|
function processUsers(lastId) {
|
||||||
|
// specify a query to limit the affected users (empty for all users):
|
||||||
|
var query = {
|
||||||
|
'migration':{$ne:migrationName},
|
||||||
|
'auth.timestamps.loggedin':{$gt:new Date('2017-11-01')},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (lastId) {
|
||||||
|
query._id = {
|
||||||
|
$gt: lastId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dbUsers.find(query, {
|
||||||
|
sort: {_id: 1},
|
||||||
|
limit: 250,
|
||||||
|
fields: [
|
||||||
|
'items.pets',
|
||||||
|
'items.mounts',
|
||||||
|
] // specify fields we are interested in to limit retrieved data (empty if we're not reading data):
|
||||||
|
})
|
||||||
|
.then(updateUsers)
|
||||||
|
.catch(function (err) {
|
||||||
|
console.log(err);
|
||||||
|
return exiting(1, 'ERROR! ' + err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var progressCount = 1000;
|
||||||
|
var count = 0;
|
||||||
|
|
||||||
|
function updateUsers (users) {
|
||||||
|
if (!users || users.length === 0) {
|
||||||
|
console.warn('All appropriate users found and modified.');
|
||||||
|
displayData();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var userPromises = users.map(updateUser);
|
||||||
|
var lastUser = users[users.length - 1];
|
||||||
|
|
||||||
|
return Promise.all(userPromises)
|
||||||
|
.then(function () {
|
||||||
|
processUsers(lastUser._id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateUser (user) {
|
||||||
|
count++;
|
||||||
|
|
||||||
|
var set = {};
|
||||||
|
|
||||||
|
if (user && user.items && user.items.mounts && user.items.mounts['Turkey-Gilded']) {
|
||||||
|
set = {
|
||||||
|
migration: migrationName,
|
||||||
|
'items.gear.owned.head_special_turkeyHelmBase': false,
|
||||||
|
'items.gear.owned.armor_special_turkeyArmorBase': false,
|
||||||
|
'items.gear.owned.back_special_turkeyTailBase': false,
|
||||||
|
};
|
||||||
|
var push = [
|
||||||
|
{
|
||||||
|
type: 'marketGear',
|
||||||
|
path: 'gear.flat.head_special_turkeyHelmBase',
|
||||||
|
_id: monk.id(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'marketGear',
|
||||||
|
path: 'gear.flat.armor_special_turkeyArmorBase',
|
||||||
|
_id: monk.id(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'marketGear',
|
||||||
|
path: 'gear.flat.back_special_turkeyTailBase',
|
||||||
|
_id: monk.id(),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
} else if (user && user.items && user.items.pets && user.items.pets['Turkey-Gilded']) {
|
||||||
|
set = {'migration':migrationName, 'items.mounts.Turkey-Gilded':true};
|
||||||
|
} else if (user && user.items && user.items.mounts && user.items.mounts['Turkey-Base']) {
|
||||||
|
set = {'migration':migrationName, 'items.pets.Turkey-Gilded':5};
|
||||||
|
} else if (user && user.items && user.items.pets && user.items.pets['Turkey-Base']) {
|
||||||
|
set = {'migration':migrationName, 'items.mounts.Turkey-Base':true};
|
||||||
|
} else {
|
||||||
|
set = {'migration':migrationName, 'items.pets.Turkey-Base':5};
|
||||||
|
}
|
||||||
|
|
||||||
|
dbUsers.update({_id: user._id}, {$set: set});
|
||||||
|
if (push) {
|
||||||
|
dbUsers.update({_id: user._id}, {$push: {pinnedItems: {$each: push}}});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count % progressCount == 0) console.warn(count + ' ' + user._id);
|
||||||
|
if (user._id == authorUuid) console.warn(authorName + ' processed');
|
||||||
|
}
|
||||||
|
|
||||||
|
function displayData() {
|
||||||
|
console.warn('\n' + count + ' users processed\n');
|
||||||
|
return exiting(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function exiting(code, msg) {
|
||||||
|
code = code || 0; // 0 = success
|
||||||
|
if (code && !msg) { msg = 'ERROR!'; }
|
||||||
|
if (msg) {
|
||||||
|
if (code) { console.error(msg); }
|
||||||
|
else { console.log( msg); }
|
||||||
|
}
|
||||||
|
process.exit(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = processUsers;
|
||||||
103
migrations/20171230_nye_hats.js
Normal file
103
migrations/20171230_nye_hats.js
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
var migrationName = '20171230_nye_hats.js';
|
||||||
|
var authorName = 'Sabe'; // in case script author needs to know when their ...
|
||||||
|
var authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; //... own data is done
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Award New Year's Eve party hats to users in sequence
|
||||||
|
*/
|
||||||
|
|
||||||
|
var monk = require('monk');
|
||||||
|
var connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
|
||||||
|
var dbUsers = monk(connectionString).get('users', { castIds: false });
|
||||||
|
|
||||||
|
function processUsers(lastId) {
|
||||||
|
// specify a query to limit the affected users (empty for all users):
|
||||||
|
var query = {
|
||||||
|
'migration': {$ne:migrationName},
|
||||||
|
'auth.timestamps.loggedin': {$gt:new Date('2017-11-30')},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (lastId) {
|
||||||
|
query._id = {
|
||||||
|
$gt: lastId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dbUsers.find(query, {
|
||||||
|
sort: {_id: 1},
|
||||||
|
limit: 250,
|
||||||
|
fields: [
|
||||||
|
'items.gear.owned',
|
||||||
|
] // specify fields we are interested in to limit retrieved data (empty if we're not reading data):
|
||||||
|
})
|
||||||
|
.then(updateUsers)
|
||||||
|
.catch(function (err) {
|
||||||
|
console.log(err);
|
||||||
|
return exiting(1, 'ERROR! ' + err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var progressCount = 1000;
|
||||||
|
var count = 0;
|
||||||
|
|
||||||
|
function updateUsers (users) {
|
||||||
|
if (!users || users.length === 0) {
|
||||||
|
console.warn('All appropriate users found and modified.');
|
||||||
|
displayData();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var userPromises = users.map(updateUser);
|
||||||
|
var lastUser = users[users.length - 1];
|
||||||
|
|
||||||
|
return Promise.all(userPromises)
|
||||||
|
.then(function () {
|
||||||
|
processUsers(lastUser._id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateUser (user) {
|
||||||
|
count++;
|
||||||
|
|
||||||
|
var set = {};
|
||||||
|
var push = {};
|
||||||
|
|
||||||
|
if (typeof user.items.gear.owned.head_special_nye2016 !== 'undefined') {
|
||||||
|
set = {'migration':migrationName, 'items.gear.owned.head_special_nye2017':false};
|
||||||
|
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.head_special_nye2017', '_id': monk.id()}};
|
||||||
|
} else if (typeof user.items.gear.owned.head_special_nye2015 !== 'undefined') {
|
||||||
|
set = {'migration':migrationName, 'items.gear.owned.head_special_nye2016':false};
|
||||||
|
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.head_special_nye2016', '_id': monk.id()}};
|
||||||
|
} else if (typeof user.items.gear.owned.head_special_nye2014 !== 'undefined') {
|
||||||
|
set = {'migration':migrationName, 'items.gear.owned.head_special_nye2015':false};
|
||||||
|
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.head_special_nye2015', '_id': monk.id()}};
|
||||||
|
} else if (typeof user.items.gear.owned.head_special_nye !== 'undefined') {
|
||||||
|
set = {'migration':migrationName, 'items.gear.owned.head_special_nye2014':false};
|
||||||
|
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.head_special_nye2014', '_id': monk.id()}};
|
||||||
|
} else {
|
||||||
|
set = {'migration':migrationName, 'items.gear.owned.head_special_nye':false};
|
||||||
|
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.head_special_nye', '_id': monk.id()}};
|
||||||
|
}
|
||||||
|
|
||||||
|
dbUsers.update({_id: user._id}, {$set: set, $push: push});
|
||||||
|
|
||||||
|
if (count % progressCount == 0) console.warn(count + ' ' + user._id);
|
||||||
|
if (user._id == authorUuid) console.warn(authorName + ' processed');
|
||||||
|
}
|
||||||
|
|
||||||
|
function displayData() {
|
||||||
|
console.warn('\n' + count + ' users processed\n');
|
||||||
|
return exiting(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function exiting(code, msg) {
|
||||||
|
code = code || 0; // 0 = success
|
||||||
|
if (code && !msg) { msg = 'ERROR!'; }
|
||||||
|
if (msg) {
|
||||||
|
if (code) { console.error(msg); }
|
||||||
|
else { console.log( msg); }
|
||||||
|
}
|
||||||
|
process.exit(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = processUsers;
|
||||||
79
migrations/20180110_nextPaymentProcessing.js
Normal file
79
migrations/20180110_nextPaymentProcessing.js
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
/*
|
||||||
|
* Convert purchased.plan.nextPaymentProcessing from a double to a date field for Apple subscribers
|
||||||
|
*/
|
||||||
|
|
||||||
|
var monk = require('monk');
|
||||||
|
var connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
|
||||||
|
var dbUsers = monk(connectionString).get('users', { castIds: false });
|
||||||
|
|
||||||
|
function processUsers(lastId) {
|
||||||
|
// specify a query to limit the affected users (empty for all users):
|
||||||
|
var query = {
|
||||||
|
'purchased.plan.paymentMethod': "Apple",
|
||||||
|
'purchased.plan.nextPaymentProcessing': {$type: 'double'},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (lastId) {
|
||||||
|
query._id = {
|
||||||
|
$gt: lastId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dbUsers.find(query, {
|
||||||
|
sort: {_id: 1},
|
||||||
|
limit: 250,
|
||||||
|
})
|
||||||
|
.then(updateUsers)
|
||||||
|
.catch(function (err) {
|
||||||
|
console.log(err);
|
||||||
|
return exiting(1, 'ERROR! ' + err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var progressCount = 100;
|
||||||
|
var count = 0;
|
||||||
|
|
||||||
|
function updateUsers (users) {
|
||||||
|
if (!users || users.length === 0) {
|
||||||
|
console.warn('All appropriate users found and modified.');
|
||||||
|
displayData();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var userPromises = users.map(updateUser);
|
||||||
|
var lastUser = users[users.length - 1];
|
||||||
|
|
||||||
|
return Promise.all(userPromises)
|
||||||
|
.then(function () {
|
||||||
|
processUsers(lastUser._id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateUser (user) {
|
||||||
|
count++;
|
||||||
|
|
||||||
|
var set = {
|
||||||
|
'purchased.plan.nextPaymentProcessing': new Date(user.purchased.plan.nextPaymentProcessing),
|
||||||
|
};
|
||||||
|
|
||||||
|
dbUsers.update({_id: user._id}, {$set: set});
|
||||||
|
|
||||||
|
if (count % progressCount == 0) console.warn(count + ' ' + user._id);
|
||||||
|
}
|
||||||
|
|
||||||
|
function displayData() {
|
||||||
|
console.warn('\n' + count + ' users processed\n');
|
||||||
|
return exiting(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function exiting(code, msg) {
|
||||||
|
code = code || 0; // 0 = success
|
||||||
|
if (code && !msg) { msg = 'ERROR!'; }
|
||||||
|
if (msg) {
|
||||||
|
if (code) { console.error(msg); }
|
||||||
|
else { console.log( msg); }
|
||||||
|
}
|
||||||
|
process.exit(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = processUsers;
|
||||||
93
migrations/20180125_clean_new_notifications.js
Normal file
93
migrations/20180125_clean_new_notifications.js
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
const UserNotification = require('../website/server/models/userNotification').model;
|
||||||
|
const content = require('../website/common/script/content/index');
|
||||||
|
|
||||||
|
const migrationName = '20180125_clean_new_migrations';
|
||||||
|
const authorName = 'paglias'; // in case script author needs to know when their ...
|
||||||
|
const authorUuid = 'ed4c688c-6652-4a92-9d03-a5a79844174a'; // ... own data is done
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Clean new migration types for processed users
|
||||||
|
*/
|
||||||
|
|
||||||
|
const monk = require('monk');
|
||||||
|
const connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
|
||||||
|
const dbUsers = monk(connectionString).get('users', { castIds: false });
|
||||||
|
|
||||||
|
const progressCount = 1000;
|
||||||
|
let count = 0;
|
||||||
|
|
||||||
|
function updateUser (user) {
|
||||||
|
count++;
|
||||||
|
|
||||||
|
const types = ['NEW_MYSTERY_ITEMS', 'CARD_RECEIVED', 'NEW_CHAT_MESSAGE'];
|
||||||
|
|
||||||
|
dbUsers.update({_id: user._id}, {
|
||||||
|
$pull: {notifications: { type: {$in: types } } },
|
||||||
|
$set: {migration: migrationName},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (count % progressCount === 0) console.warn(`${count } ${ user._id}`);
|
||||||
|
if (user._id === authorUuid) console.warn(`${authorName } processed`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function exiting (code, msg) {
|
||||||
|
code = code || 0; // 0 = success
|
||||||
|
if (code && !msg) {
|
||||||
|
msg = 'ERROR!';
|
||||||
|
}
|
||||||
|
if (msg) {
|
||||||
|
if (code) {
|
||||||
|
console.error(msg);
|
||||||
|
} else {
|
||||||
|
console.log(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
process.exit(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
function displayData () {
|
||||||
|
console.warn(`\n${ count } users processed\n`);
|
||||||
|
return exiting(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateUsers (users) {
|
||||||
|
if (!users || users.length === 0) {
|
||||||
|
console.warn('All appropriate users found and modified.');
|
||||||
|
displayData();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const userPromises = users.map(updateUser);
|
||||||
|
const lastUser = users[users.length - 1];
|
||||||
|
|
||||||
|
return Promise.all(userPromises)
|
||||||
|
.then(() => {
|
||||||
|
processUsers(lastUser._id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function processUsers (lastId) {
|
||||||
|
// specify a query to limit the affected users (empty for all users):
|
||||||
|
const query = {
|
||||||
|
migration: {$ne: migrationName},
|
||||||
|
'auth.timestamps.loggedin': {$gt: new Date('2010-01-24')},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (lastId) {
|
||||||
|
query._id = {
|
||||||
|
$gt: lastId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
dbUsers.find(query, {
|
||||||
|
sort: {_id: 1},
|
||||||
|
limit: 250,
|
||||||
|
})
|
||||||
|
.then(updateUsers)
|
||||||
|
.catch((err) => {
|
||||||
|
console.log(err);
|
||||||
|
return exiting(1, `ERROR! ${ err}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = processUsers;
|
||||||
149
migrations/20180125_notifications.js
Normal file
149
migrations/20180125_notifications.js
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
const UserNotification = require('../website/server/models/userNotification').model;
|
||||||
|
const content = require('../website/common/script/content/index');
|
||||||
|
|
||||||
|
const migrationName = '20180125_migrations-v2';
|
||||||
|
const authorName = 'paglias'; // in case script author needs to know when their ...
|
||||||
|
const authorUuid = 'ed4c688c-6652-4a92-9d03-a5a79844174a'; // ... own data is done
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Migrate to new notifications system
|
||||||
|
*/
|
||||||
|
|
||||||
|
const monk = require('monk');
|
||||||
|
const connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
|
||||||
|
const dbUsers = monk(connectionString).get('users', { castIds: false });
|
||||||
|
|
||||||
|
const progressCount = 1000;
|
||||||
|
let count = 0;
|
||||||
|
|
||||||
|
function updateUser (user) {
|
||||||
|
count++;
|
||||||
|
|
||||||
|
const notifications = [];
|
||||||
|
|
||||||
|
// UNALLOCATED_STATS_POINTS skipped because added on each save
|
||||||
|
// NEW_STUFF skipped because it's a new type
|
||||||
|
// GROUP_TASK_NEEDS_WORK because it's a new type
|
||||||
|
// NEW_INBOX_MESSAGE not implemented yet
|
||||||
|
|
||||||
|
|
||||||
|
// NEW_MYSTERY_ITEMS
|
||||||
|
const mysteryItems = user.purchased && user.purchased.plan && user.purchased.plan.mysteryItems;
|
||||||
|
if (Array.isArray(mysteryItems) && mysteryItems.length > 0) {
|
||||||
|
const newMysteryNotif = new UserNotification({
|
||||||
|
type: 'NEW_MYSTERY_ITEMS',
|
||||||
|
data: {
|
||||||
|
items: mysteryItems,
|
||||||
|
},
|
||||||
|
}).toJSON();
|
||||||
|
notifications.push(newMysteryNotif);
|
||||||
|
}
|
||||||
|
|
||||||
|
// CARD_RECEIVED
|
||||||
|
Object.keys(content.cardTypes).forEach(cardType => {
|
||||||
|
const existingCards = user.items.special[`${cardType}Received`] || [];
|
||||||
|
existingCards.forEach(sender => {
|
||||||
|
const newNotif = new UserNotification({
|
||||||
|
type: 'CARD_RECEIVED',
|
||||||
|
data: {
|
||||||
|
card: cardType,
|
||||||
|
from: {
|
||||||
|
// id is missing in old notifications
|
||||||
|
name: sender,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}).toJSON();
|
||||||
|
|
||||||
|
notifications.push(newNotif);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// NEW_CHAT_MESSAGE
|
||||||
|
Object.keys(user.newMessages).forEach(groupId => {
|
||||||
|
const existingNotif = user.newMessages[groupId];
|
||||||
|
|
||||||
|
if (existingNotif) {
|
||||||
|
const newNotif = new UserNotification({
|
||||||
|
type: 'NEW_CHAT_MESSAGE',
|
||||||
|
data: {
|
||||||
|
group: {
|
||||||
|
id: groupId,
|
||||||
|
name: existingNotif.name,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}).toJSON();
|
||||||
|
|
||||||
|
notifications.push(newNotif);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
dbUsers.update({_id: user._id}, {
|
||||||
|
$push: {notifications: { $each: notifications } },
|
||||||
|
$set: {migration: migrationName},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (count % progressCount === 0) console.warn(`${count } ${ user._id}`);
|
||||||
|
if (user._id === authorUuid) console.warn(`${authorName } processed`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function exiting (code, msg) {
|
||||||
|
code = code || 0; // 0 = success
|
||||||
|
if (code && !msg) {
|
||||||
|
msg = 'ERROR!';
|
||||||
|
}
|
||||||
|
if (msg) {
|
||||||
|
if (code) {
|
||||||
|
console.error(msg);
|
||||||
|
} else {
|
||||||
|
console.log(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
process.exit(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
function displayData () {
|
||||||
|
console.warn(`\n${ count } users processed\n`);
|
||||||
|
return exiting(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateUsers (users) {
|
||||||
|
if (!users || users.length === 0) {
|
||||||
|
console.warn('All appropriate users found and modified.');
|
||||||
|
displayData();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const userPromises = users.map(updateUser);
|
||||||
|
const lastUser = users[users.length - 1];
|
||||||
|
|
||||||
|
return Promise.all(userPromises)
|
||||||
|
.then(() => {
|
||||||
|
processUsers(lastUser._id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function processUsers (lastId) {
|
||||||
|
// specify a query to limit the affected users (empty for all users):
|
||||||
|
const query = {
|
||||||
|
migration: {$ne: migrationName},
|
||||||
|
'auth.timestamps.loggedin': {$gt: new Date('2010-01-24')},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (lastId) {
|
||||||
|
query._id = {
|
||||||
|
$gt: lastId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
dbUsers.find(query, {
|
||||||
|
sort: {_id: 1},
|
||||||
|
limit: 250,
|
||||||
|
})
|
||||||
|
.then(updateUsers)
|
||||||
|
.catch((err) => {
|
||||||
|
console.log(err);
|
||||||
|
return exiting(1, `ERROR! ${ err}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = processUsers;
|
||||||
118
migrations/20180130_habit_birthday.js
Normal file
118
migrations/20180130_habit_birthday.js
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
var migrationName = '20180130_habit_birthday.js';
|
||||||
|
var authorName = 'Sabe'; // in case script author needs to know when their ...
|
||||||
|
var authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; //... own data is done
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Award party robes: most recent user doesn't have of 2014-2018. Also cake!
|
||||||
|
*/
|
||||||
|
|
||||||
|
var monk = require('monk');
|
||||||
|
var connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
|
||||||
|
var dbUsers = monk(connectionString).get('users', { castIds: false });
|
||||||
|
|
||||||
|
function processUsers(lastId) {
|
||||||
|
// specify a query to limit the affected users (empty for all users):
|
||||||
|
var query = {
|
||||||
|
'migration':{$ne:migrationName},
|
||||||
|
'auth.timestamps.loggedin':{$gt:new Date('2018-01-01')}, // remove after first run to cover remaining users
|
||||||
|
};
|
||||||
|
|
||||||
|
if (lastId) {
|
||||||
|
query._id = {
|
||||||
|
$gt: lastId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dbUsers.find(query, {
|
||||||
|
sort: {_id: 1},
|
||||||
|
limit: 250,
|
||||||
|
fields: [ // specify fields we are interested in to limit retrieved data (empty if we're not reading data)
|
||||||
|
'items.gear.owned'
|
||||||
|
],
|
||||||
|
})
|
||||||
|
.then(updateUsers)
|
||||||
|
.catch(function (err) {
|
||||||
|
console.log(err);
|
||||||
|
return exiting(1, 'ERROR! ' + err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var progressCount = 1000;
|
||||||
|
var count = 0;
|
||||||
|
|
||||||
|
function updateUsers (users) {
|
||||||
|
if (!users || users.length === 0) {
|
||||||
|
console.warn('All appropriate users found and modified.');
|
||||||
|
displayData();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var userPromises = users.map(updateUser);
|
||||||
|
var lastUser = users[users.length - 1];
|
||||||
|
|
||||||
|
return Promise.all(userPromises)
|
||||||
|
.then(function () {
|
||||||
|
processUsers(lastUser._id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateUser (user) {
|
||||||
|
count++;
|
||||||
|
|
||||||
|
var push;
|
||||||
|
var set = {'migration':migrationName};
|
||||||
|
|
||||||
|
if (user.items && user.items.gear && user.items.gear.owned && user.items.gear.owned.hasOwnProperty('armor_special_birthday2017')) {
|
||||||
|
set['items.gear.owned.armor_special_birthday2018'] = false;
|
||||||
|
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.armor_special_birthday2018', '_id': monk.id()}};
|
||||||
|
} else if (user.items && user.items.gear && user.items.gear.owned && user.items.gear.owned.hasOwnProperty('armor_special_birthday2016')) {
|
||||||
|
set['items.gear.owned.armor_special_birthday2017'] = false;
|
||||||
|
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.armor_special_birthday2017', '_id': monk.id()}};
|
||||||
|
} else if (user.items && user.items.gear && user.items.gear.owned && user.items.gear.owned.hasOwnProperty('armor_special_birthday2015')) {
|
||||||
|
set['items.gear.owned.armor_special_birthday2016'] = false;
|
||||||
|
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.armor_special_birthday2016', '_id': monk.id()}};
|
||||||
|
} else if (user.items && user.items.gear && user.items.gear.owned && user.items.gear.owned.hasOwnProperty('armor_special_birthday')) {
|
||||||
|
set['items.gear.owned.armor_special_birthday2015'] = false;
|
||||||
|
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.armor_special_birthday2015', '_id': monk.id()}};
|
||||||
|
} else {
|
||||||
|
set['items.gear.owned.armor_special_birthday'] = false;
|
||||||
|
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.armor_special_birthday', '_id': monk.id()}};
|
||||||
|
}
|
||||||
|
|
||||||
|
var inc = {
|
||||||
|
'items.food.Cake_Skeleton':1,
|
||||||
|
'items.food.Cake_Base':1,
|
||||||
|
'items.food.Cake_CottonCandyBlue':1,
|
||||||
|
'items.food.Cake_CottonCandyPink':1,
|
||||||
|
'items.food.Cake_Shade':1,
|
||||||
|
'items.food.Cake_White':1,
|
||||||
|
'items.food.Cake_Golden':1,
|
||||||
|
'items.food.Cake_Zombie':1,
|
||||||
|
'items.food.Cake_Desert':1,
|
||||||
|
'items.food.Cake_Red':1,
|
||||||
|
'achievements.habitBirthdays':1
|
||||||
|
};
|
||||||
|
|
||||||
|
dbUsers.update({_id: user._id}, {$set: set, $inc: inc, $push: push});
|
||||||
|
|
||||||
|
if (count % progressCount == 0) console.warn(count + ' ' + user._id);
|
||||||
|
if (user._id == authorUuid) console.warn(authorName + ' processed');
|
||||||
|
}
|
||||||
|
|
||||||
|
function displayData() {
|
||||||
|
console.warn('\n' + count + ' users processed\n');
|
||||||
|
return exiting(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function exiting(code, msg) {
|
||||||
|
code = code || 0; // 0 = success
|
||||||
|
if (code && !msg) { msg = 'ERROR!'; }
|
||||||
|
if (msg) {
|
||||||
|
if (code) { console.error(msg); }
|
||||||
|
else { console.log( msg); }
|
||||||
|
}
|
||||||
|
process.exit(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = processUsers;
|
||||||
|
|
||||||
59
migrations/docs/mongo-indexes.md
Normal file
59
migrations/docs/mongo-indexes.md
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
# Indexes
|
||||||
|
|
||||||
|
This file contains a list of indexes that are on Habitica's production Mongo server.
|
||||||
|
If we ever have an issue, use this list to reindex.
|
||||||
|
|
||||||
|
## Challenges
|
||||||
|
- `{ "group": 1, "official": -1, "timestamp": -1 }`
|
||||||
|
- `{ "leader": 1, "official": -1, "timestamp": -1 }`
|
||||||
|
- `{ "official": -1, "timestamp": -1 }`
|
||||||
|
|
||||||
|
## Groups
|
||||||
|
- `{ "privacy": 1, "type": 1, "memberCount": -1 }`
|
||||||
|
- `{ "privacy": 1 }`
|
||||||
|
- `{ "purchased.plan.customerId": 1 }`
|
||||||
|
- `{ "purchased.plan.paymentMethod": 1 }`
|
||||||
|
- `{ "purchased.plan.planId": 1, "purchased.plan.dateTerminated": 1 }`
|
||||||
|
- `{ "type": 1, "memberCount": -1, "_id": 1 }`
|
||||||
|
- `{ "type": 1 }`
|
||||||
|
|
||||||
|
## Tasks
|
||||||
|
- `{ "challenge.id": 1 }`
|
||||||
|
- `{ "challenge.taskId": 1 }`
|
||||||
|
- `{ "group.id": 1 }`
|
||||||
|
- `{ "group.taskId": 1 }`
|
||||||
|
- `{ "type": 1, "everyX": 1, "frequency": 1 }`
|
||||||
|
- `{ "userId": 1 }`
|
||||||
|
- `{ "yesterDaily": 1, "type": 1 }`
|
||||||
|
|
||||||
|
## Users
|
||||||
|
- `{ "_id": 1, "apiToken": 1 }`
|
||||||
|
- `{ "auth.facebook.emails.value": 1 }`
|
||||||
|
- `{ "auth.facebook.id": 1 }`
|
||||||
|
- `{ "auth.google.emails.value": 1 }`
|
||||||
|
- `{ "auth.google.id": 1 }`
|
||||||
|
- `{ "auth.local.email": 1 }`
|
||||||
|
- `{ "auth.local.lowerCaseUsername": 1 }`
|
||||||
|
- `{ "auth.local.username": 1 }`
|
||||||
|
- `{ "auth.timestamps.created": 1 }`
|
||||||
|
- `{ "auth.timestamps.loggedin": 1, "_lastPushNotification": 1, "preferences.timezoneOffset": 1 }`
|
||||||
|
- `{ "auth.timestamps.loggedin": 1 }`
|
||||||
|
- `{ "backer.tier": -1 }`
|
||||||
|
- `{ "challenges": 1, "_id": 1 }`
|
||||||
|
- `{ "contributor.admin": 1, "contributor.level": -1, "backer.npc": -1, "profile.name": 1 }`
|
||||||
|
- `{ "contributor.level": 1 }`
|
||||||
|
- `{ "flags.newStuff": 1 }`
|
||||||
|
- `{ "flags.armoireEmpty": 1 }`
|
||||||
|
- `{ "guilds": 1, "_id": 1 }`
|
||||||
|
- `{ "invitations.guilds.id": 1, "_id": 1 }`
|
||||||
|
- `{ "invitations.party.id": 1 }`
|
||||||
|
- `{ "loginIncentives": 1 }`
|
||||||
|
- `{ "migration": 1 }`
|
||||||
|
- `{ "party._id": 1, "_id": 1 }`
|
||||||
|
- `{ "preferences.sleep": 1, "_id": 1, "flags.lastWeeklyRecap": 1, "preferences.emailNotifications.unsubscribeFromAll": 1, "preferences.emailNotifications.weeklyRecaps": 1 }`
|
||||||
|
- `{ "preferences.sleep": 1, "_id": 1, "lastCron": 1, "preferences.emailNotifications.importantAnnouncements": 1, "preferences.emailNotifications.unsubscribeFromAll": 1, "flags.recaptureEmailsPhase": 1 }`
|
||||||
|
- `{ "profile.name": 1 }`
|
||||||
|
- `{ "purchased.plan.customerId": 1 }`
|
||||||
|
- `{ "purchased.plan.paymentMethod": 1 }`
|
||||||
|
- `{ "stats.score.overall": 1 }`
|
||||||
|
- `{ "webhooks.type": 1 }`
|
||||||
@@ -17,8 +17,5 @@ function setUpServer () {
|
|||||||
setUpServer();
|
setUpServer();
|
||||||
|
|
||||||
// Replace this with your migration
|
// Replace this with your migration
|
||||||
var processUsers = require('./groups/update-groups-with-group-plans');
|
const processUsers = require('./20180125_clean_new_notifications.js');
|
||||||
processUsers()
|
processUsers();
|
||||||
.catch(function (err) {
|
|
||||||
console.log(err)
|
|
||||||
})
|
|
||||||
|
|||||||
@@ -1,10 +1,23 @@
|
|||||||
|
var UserNotification = require('../website/server/models/userNotification').model
|
||||||
|
|
||||||
var _id = '';
|
var _id = '';
|
||||||
|
|
||||||
|
var items = ['back_mystery_201801','headAccessory_mystery_201801']
|
||||||
|
|
||||||
var update = {
|
var update = {
|
||||||
$addToSet: {
|
$addToSet: {
|
||||||
'purchased.plan.mysteryItems':{
|
'purchased.plan.mysteryItems':{
|
||||||
$each:['shield_mystery_201708','weapon_mystery_201708']
|
$each: items,
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
$push: {
|
||||||
|
notifications: (new UserNotification({
|
||||||
|
type: 'NEW_MYSTERY_ITEMS',
|
||||||
|
data: {
|
||||||
|
items: items,
|
||||||
|
},
|
||||||
|
})).toJSON(),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
/*var update = {
|
/*var update = {
|
||||||
|
|||||||
98
migrations/s3-upload.js
Normal file
98
migrations/s3-upload.js
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
let Bluebird = require('bluebird');
|
||||||
|
let request = require('superagent');
|
||||||
|
let last = require('lodash/last');
|
||||||
|
let AWS = require('aws-sdk');
|
||||||
|
|
||||||
|
let config = require('../config');
|
||||||
|
const S3_DIRECTORY = 'mobileApp/images'; //config.S3.SPRITES_DIRECTORY;
|
||||||
|
|
||||||
|
AWS.config.update({
|
||||||
|
accessKeyId: config.S3.accessKeyId,
|
||||||
|
secretAccessKey: config.S3.secretAccessKey,
|
||||||
|
// region: config.get('S3_REGION'),
|
||||||
|
});
|
||||||
|
|
||||||
|
let BUCKET_NAME = config.S3.bucket;
|
||||||
|
let s3 = new AWS.S3();
|
||||||
|
|
||||||
|
// Adapted from http://stackoverflow.com/a/22210077/2601552
|
||||||
|
function uploadFile (buffer, fileName) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
s3.putObject({
|
||||||
|
Body: buffer,
|
||||||
|
Key: fileName,
|
||||||
|
Bucket: BUCKET_NAME,
|
||||||
|
}, (error) => {
|
||||||
|
if (error) {
|
||||||
|
reject(error);
|
||||||
|
} else {
|
||||||
|
// console.info(`${fileName} uploaded to ${BUCKET_NAME} succesfully.`);
|
||||||
|
resolve(fileName);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFileName (file) {
|
||||||
|
let piecesOfPath = file.split('/');
|
||||||
|
let name = last(piecesOfPath);
|
||||||
|
let fullName = S3_DIRECTORY + name;
|
||||||
|
|
||||||
|
return fullName;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFileFromUrl (url) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
request.get(url).end((err, res) => {
|
||||||
|
if (err) return reject(err);
|
||||||
|
let file = res.body;
|
||||||
|
resolve(file);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let commit = '78f94e365c72cc58f66857d5941105638db7d35c';
|
||||||
|
commit = 'df0dbaba636c9ce424cc7040f7bd7fc1aa311015';
|
||||||
|
let gihuburl = `https://api.github.com/repos/HabitRPG/habitica/commits/${commit}`
|
||||||
|
|
||||||
|
|
||||||
|
let currentIndex = 0;
|
||||||
|
|
||||||
|
function uploadToS3(start, end, filesUrls) {
|
||||||
|
let urls = filesUrls.slice(start, end);
|
||||||
|
|
||||||
|
if (urls.length === 0) {
|
||||||
|
console.log("done");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let promises = urls.map(fullUrl => {
|
||||||
|
return getFileFromUrl(fullUrl)
|
||||||
|
.then((buffer) => {
|
||||||
|
return uploadFile(buffer, getFileName(fullUrl));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
console.log(promises.length)
|
||||||
|
|
||||||
|
return Bluebird.all(promises)
|
||||||
|
.then(() => {
|
||||||
|
currentIndex += 50;
|
||||||
|
uploadToS3(currentIndex, currentIndex + 50, filesUrls);
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
console.log(e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
request.get(gihuburl)
|
||||||
|
.end((err, res) => {
|
||||||
|
console.log(err);
|
||||||
|
let files = res.body.files;
|
||||||
|
|
||||||
|
let filesUrls = [''];
|
||||||
|
filesUrls = files.map(file => {
|
||||||
|
return file.raw_url;
|
||||||
|
})
|
||||||
|
|
||||||
|
uploadToS3(currentIndex, currentIndex + 50, filesUrls);
|
||||||
|
});
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
var migrationName = '20170502_takeThis.js'; // Update per month
|
var migrationName = '20180102_takeThis.js'; // Update per month
|
||||||
var authorName = 'Sabe'; // in case script author needs to know when their ...
|
var authorName = 'Sabe'; // in case script author needs to know when their ...
|
||||||
var authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; //... own data is done
|
var authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; //... own data is done
|
||||||
|
|
||||||
@@ -7,14 +7,14 @@ var authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; //... own data is done
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
var monk = require('monk');
|
var monk = require('monk');
|
||||||
var connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
|
var connectionString = 'mongodb://sabrecat:z8e8jyRA8CTofMQ@ds013393-a0.mlab.com:13393/habitica?auto_reconnect=true';
|
||||||
var dbUsers = monk(connectionString).get('users', { castIds: false });
|
var dbUsers = monk(connectionString).get('users', { castIds: false });
|
||||||
|
|
||||||
function processUsers(lastId) {
|
function processUsers(lastId) {
|
||||||
// specify a query to limit the affected users (empty for all users):
|
// specify a query to limit the affected users (empty for all users):
|
||||||
var query = {
|
var query = {
|
||||||
'migration':{$ne:migrationName},
|
'migration':{$ne:migrationName},
|
||||||
'challenges':{$in:['69999331-d4ea-45a0-8c3f-f725d22b56c8']} // Update per month
|
'challenges':{$in:['5f70ce5b-2d82-4114-8e44-ca65615aae62']} // Update per month
|
||||||
};
|
};
|
||||||
|
|
||||||
if (lastId) {
|
if (lastId) {
|
||||||
@@ -65,19 +65,29 @@ function updateUser (user) {
|
|||||||
set = {'migration':migrationName};
|
set = {'migration':migrationName};
|
||||||
} else if (typeof user.items.gear.owned.body_special_takeThis !== 'undefined') {
|
} else if (typeof user.items.gear.owned.body_special_takeThis !== 'undefined') {
|
||||||
set = {'migration':migrationName, 'items.gear.owned.back_special_takeThis':false};
|
set = {'migration':migrationName, 'items.gear.owned.back_special_takeThis':false};
|
||||||
|
var push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.back_special_takeThis', '_id': monk.id()}};
|
||||||
} else if (typeof user.items.gear.owned.head_special_takeThis !== 'undefined') {
|
} else if (typeof user.items.gear.owned.head_special_takeThis !== 'undefined') {
|
||||||
set = {'migration':migrationName, 'items.gear.owned.body_special_takeThis':false};
|
set = {'migration':migrationName, 'items.gear.owned.body_special_takeThis':false};
|
||||||
|
var push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.body_special_takeThis', '_id': monk.id()}};
|
||||||
} else if (typeof user.items.gear.owned.armor_special_takeThis !== 'undefined') {
|
} else if (typeof user.items.gear.owned.armor_special_takeThis !== 'undefined') {
|
||||||
set = {'migration':migrationName, 'items.gear.owned.head_special_takeThis':false};
|
set = {'migration':migrationName, 'items.gear.owned.head_special_takeThis':false};
|
||||||
|
var push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.head_special_takeThis', '_id': monk.id()}};
|
||||||
} else if (typeof user.items.gear.owned.weapon_special_takeThis !== 'undefined') {
|
} else if (typeof user.items.gear.owned.weapon_special_takeThis !== 'undefined') {
|
||||||
set = {'migration':migrationName, 'items.gear.owned.armor_special_takeThis':false};
|
set = {'migration':migrationName, 'items.gear.owned.armor_special_takeThis':false};
|
||||||
|
var push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.armor_special_takeThis', '_id': monk.id()}};
|
||||||
} else if (typeof user.items.gear.owned.shield_special_takeThis !== 'undefined') {
|
} else if (typeof user.items.gear.owned.shield_special_takeThis !== 'undefined') {
|
||||||
set = {'migration':migrationName, 'items.gear.owned.weapon_special_takeThis':false};
|
set = {'migration':migrationName, 'items.gear.owned.weapon_special_takeThis':false};
|
||||||
|
var push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.weapon_special_takeThis', '_id': monk.id()}};
|
||||||
} else {
|
} else {
|
||||||
set = {'migration':migrationName, 'items.gear.owned.shield_special_takeThis':false};
|
set = {'migration':migrationName, 'items.gear.owned.shield_special_takeThis':false};
|
||||||
|
var push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.shield_special_takeThis', '_id': monk.id()}};
|
||||||
}
|
}
|
||||||
|
|
||||||
dbUsers.update({_id: user._id}, {$set:set});
|
if (push) {
|
||||||
|
dbUsers.update({_id: user._id}, {$set: set, $push: push});
|
||||||
|
} else {
|
||||||
|
dbUsers.update({_id: user._id}, {$set: set});
|
||||||
|
}
|
||||||
|
|
||||||
if (count % progressCount == 0) console.warn(count + ' ' + user._id);
|
if (count % progressCount == 0) console.warn(count + ' ' + user._id);
|
||||||
if (user._id == authorUuid) console.warn(authorName + ' processed');
|
if (user._id == authorUuid) console.warn(authorName + ' processed');
|
||||||
|
|||||||
88
migrations/tasks/tasks-set-everyX.js
Normal file
88
migrations/tasks/tasks-set-everyX.js
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
var migrationName = 'tasks-set-everyX';
|
||||||
|
var authorName = 'Sabe'; // in case script author needs to know when their ...
|
||||||
|
var authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; //... own data is done
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Iterates over all tasks and sets invalid everyX values (less than 0 or more than 9999 or not an int) field to 0
|
||||||
|
*/
|
||||||
|
|
||||||
|
var monk = require('monk');
|
||||||
|
var connectionString = 'mongodb://sabrecat:z8e8jyRA8CTofMQ@ds013393-a0.mlab.com:13393/habitica?auto_reconnect=true';
|
||||||
|
var dbTasks = monk(connectionString).get('tasks', { castIds: false });
|
||||||
|
|
||||||
|
function processTasks(lastId) {
|
||||||
|
// specify a query to limit the affected tasks (empty for all tasks):
|
||||||
|
var query = {
|
||||||
|
type: "daily",
|
||||||
|
everyX: {
|
||||||
|
$not: {
|
||||||
|
$gte: 0,
|
||||||
|
$lte: 9999,
|
||||||
|
$type: "int",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (lastId) {
|
||||||
|
query._id = {
|
||||||
|
$gt: lastId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dbTasks.find(query, {
|
||||||
|
sort: {_id: 1},
|
||||||
|
limit: 250,
|
||||||
|
fields: [],
|
||||||
|
})
|
||||||
|
.then(updateTasks)
|
||||||
|
.catch(function (err) {
|
||||||
|
console.log(err);
|
||||||
|
return exiting(1, 'ERROR! ' + err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var progressCount = 1000;
|
||||||
|
var count = 0;
|
||||||
|
|
||||||
|
function updateTasks (tasks) {
|
||||||
|
if (!tasks || tasks.length === 0) {
|
||||||
|
console.warn('All appropriate tasks found and modified.');
|
||||||
|
displayData();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var taskPromises = tasks.map(updatetask);
|
||||||
|
var lasttask = tasks[tasks.length - 1];
|
||||||
|
|
||||||
|
return Promise.all(taskPromises)
|
||||||
|
.then(function () {
|
||||||
|
processTasks(lasttask._id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updatetask (task) {
|
||||||
|
count++;
|
||||||
|
var set = {'everyX': 0};
|
||||||
|
|
||||||
|
dbTasks.update({_id: task._id}, {$set:set});
|
||||||
|
|
||||||
|
if (count % progressCount == 0) console.warn(count + ' ' + task._id);
|
||||||
|
if (task._id == authorUuid) console.warn(authorName + ' processed');
|
||||||
|
}
|
||||||
|
|
||||||
|
function displayData() {
|
||||||
|
console.warn('\n' + count + ' tasks processed\n');
|
||||||
|
return exiting(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function exiting(code, msg) {
|
||||||
|
code = code || 0; // 0 = success
|
||||||
|
if (code && !msg) { msg = 'ERROR!'; }
|
||||||
|
if (msg) {
|
||||||
|
if (code) { console.error(msg); }
|
||||||
|
else { console.log( msg); }
|
||||||
|
}
|
||||||
|
process.exit(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = processTasks;
|
||||||
38
migrations/users/account-transfer.js
Normal file
38
migrations/users/account-transfer.js
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
var migrationName = 'AccountTransfer';
|
||||||
|
var authorName = 'TheHollidayInn'; // in case script author needs to know when their ...
|
||||||
|
var authorUuid = ''; //... own data is done
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This migraition will copy user data from prod to test
|
||||||
|
*/
|
||||||
|
|
||||||
|
const monk = require('monk');
|
||||||
|
const connectionString = '';
|
||||||
|
const Users = monk(connectionString).get('users', { castIds: false });
|
||||||
|
|
||||||
|
import uniq from 'lodash/uniq';
|
||||||
|
import Bluebird from 'bluebird';
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = async function accountTransfer () {
|
||||||
|
const fromAccountId = '';
|
||||||
|
const toAccountId = '';
|
||||||
|
|
||||||
|
const fromAccount = await Users.findOne({_id: fromAccountId});
|
||||||
|
const toAccount = await Users.findOne({_id: toAccountId});
|
||||||
|
|
||||||
|
const newMounts = Object.assign({}, fromAccount.items.mounts, toAccount.items.mounts);
|
||||||
|
const newPets = Object.assign({}, fromAccount.items.pets, toAccount.items.pets);
|
||||||
|
const newBackgrounds = Object.assign({}, fromAccount.purchased.background, toAccount.purchased.background);
|
||||||
|
|
||||||
|
await Users.update({_id: toAccountId}, {
|
||||||
|
$set: {
|
||||||
|
'items.pets': newPets,
|
||||||
|
'items.mounts': newMounts,
|
||||||
|
'purchased.background': newBackgrounds,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((result) => {
|
||||||
|
console.log(result);
|
||||||
|
});
|
||||||
|
};
|
||||||
93
migrations/users/achievement-restore.js
Normal file
93
migrations/users/achievement-restore.js
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
const migrationName = 'AchievementRestore';
|
||||||
|
const authorName = 'TheHollidayInn'; // in case script author needs to know when their ...
|
||||||
|
const authorUuid = ''; //... own data is done
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This migraition will copy user data from prod to test
|
||||||
|
*/
|
||||||
|
import Bluebird from 'bluebird';
|
||||||
|
|
||||||
|
const monk = require('monk');
|
||||||
|
const connectionString = 'mongodb://localhost/new-habit';
|
||||||
|
const Users = monk(connectionString).get('users', { castIds: false });
|
||||||
|
|
||||||
|
const monkOld = require('monk');
|
||||||
|
const oldConnectionSting = 'mongodb://localhost/old-habit';
|
||||||
|
const UsersOld = monk(oldConnectionSting).get('users', { castIds: false });
|
||||||
|
|
||||||
|
function getAchievementUpdate (newUser, oldUser) {
|
||||||
|
const oldAchievements = oldUser.achievements;
|
||||||
|
const newAchievements = newUser.achievements;
|
||||||
|
|
||||||
|
let achievementsUpdate = Object.assign({}, newAchievements);
|
||||||
|
|
||||||
|
// ultimateGearSets
|
||||||
|
if (!achievementsUpdate.ultimateGearSets && oldAchievements.ultimateGearSets) {
|
||||||
|
achievementsUpdate.ultimateGearSets = oldAchievements.ultimateGearSets;
|
||||||
|
} else if (oldAchievements.ultimateGearSets) {
|
||||||
|
for (let index in oldAchievements.ultimateGearSets) {
|
||||||
|
if (oldAchievements.ultimateGearSets[index]) achievementsUpdate.ultimateGearSets[index] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// challenges
|
||||||
|
if (!newAchievements.challenges) newAchievements.challenges = [];
|
||||||
|
if (!oldAchievements.challenges) oldAchievements.challenges = [];
|
||||||
|
achievementsUpdate.challenges = newAchievements.challenges.concat(oldAchievements.challenges);
|
||||||
|
|
||||||
|
// Quests
|
||||||
|
if (!achievementsUpdate.quests) achievementsUpdate.quests = {};
|
||||||
|
for (let index in oldAchievements.quests) {
|
||||||
|
if (!achievementsUpdate.quests[index]) {
|
||||||
|
achievementsUpdate.quests[index] = oldAchievements.quests[index];
|
||||||
|
} else {
|
||||||
|
achievementsUpdate.quests[index] += oldAchievements.quests[index];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rebirth level
|
||||||
|
if (achievementsUpdate.rebirthLevel) {
|
||||||
|
achievementsUpdate.rebirthLevel = Math.max(achievementsUpdate.rebirthLevel, oldAchievements.rebirthLevel);
|
||||||
|
} else if (oldAchievements.rebirthLevel) {
|
||||||
|
achievementsUpdate.rebirthLevel = oldAchievements.rebirthLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
//All others
|
||||||
|
const indexsToIgnore = ['ultimateGearSets', 'challenges', 'quests', 'rebirthLevel'];
|
||||||
|
for (let index in oldAchievements) {
|
||||||
|
if (indexsToIgnore.indexOf(index) !== -1) continue;
|
||||||
|
|
||||||
|
if (!achievementsUpdate[index]) {
|
||||||
|
achievementsUpdate[index] = oldAchievements[index];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Number.isInteger(oldAchievements[index])) {
|
||||||
|
achievementsUpdate[index] += oldAchievements[index];
|
||||||
|
} else {
|
||||||
|
if (oldAchievements[index] === true) achievementsUpdate[index] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return achievementsUpdate;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = async function achievementRestore () {
|
||||||
|
const userIds = [
|
||||||
|
];
|
||||||
|
|
||||||
|
for (let index in userIds) {
|
||||||
|
const userId = userIds[index];
|
||||||
|
const oldUser = await UsersOld.findOne({_id: userId}, 'achievements');
|
||||||
|
const newUser = await Users.findOne({_id: userId}, 'achievements');
|
||||||
|
const achievementUpdate = getAchievementUpdate(newUser, oldUser);
|
||||||
|
await Users.update(
|
||||||
|
{_id: userId},
|
||||||
|
{
|
||||||
|
$set: {
|
||||||
|
'achievements': achievementUpdate,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
console.log(`Updated ${userId}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
13885
npm-shrinkwrap.json
generated
13885
npm-shrinkwrap.json
generated
File diff suppressed because it is too large
Load Diff
19974
package-lock.json
generated
Normal file
19974
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
87
package.json
87
package.json
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"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": "3.111.6",
|
"version": "4.24.1",
|
||||||
"main": "./website/server/index.js",
|
"main": "./website/server/index.js",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@slack/client": "^3.8.1",
|
"@slack/client": "^3.8.1",
|
||||||
@@ -14,6 +14,7 @@
|
|||||||
"autoprefixer": "^6.4.0",
|
"autoprefixer": "^6.4.0",
|
||||||
"aws-sdk": "^2.0.25",
|
"aws-sdk": "^2.0.25",
|
||||||
"axios": "^0.16.0",
|
"axios": "^0.16.0",
|
||||||
|
"axios-progress-bar": "^0.1.7",
|
||||||
"babel-core": "^6.0.0",
|
"babel-core": "^6.0.0",
|
||||||
"babel-eslint": "^7.2.3",
|
"babel-eslint": "^7.2.3",
|
||||||
"babel-loader": "^6.0.0",
|
"babel-loader": "^6.0.0",
|
||||||
@@ -26,43 +27,27 @@
|
|||||||
"babel-preset-es2015": "^6.6.0",
|
"babel-preset-es2015": "^6.6.0",
|
||||||
"babel-register": "^6.6.0",
|
"babel-register": "^6.6.0",
|
||||||
"babel-runtime": "^6.11.6",
|
"babel-runtime": "^6.11.6",
|
||||||
"babelify": "^7.2.0",
|
|
||||||
"bcrypt": "^1.0.2",
|
"bcrypt": "^1.0.2",
|
||||||
"bluebird": "^3.3.5",
|
"bluebird": "^3.3.5",
|
||||||
"body-parser": "^1.15.0",
|
"body-parser": "^1.15.0",
|
||||||
"bootstrap": "^4.0.0-alpha.6",
|
"bootstrap": "^4.0.0",
|
||||||
"bootstrap-vue": "^0.18.0",
|
"bootstrap-vue": "^1.5.0",
|
||||||
"bower": "~1.3.12",
|
|
||||||
"browserify": "~12.0.1",
|
|
||||||
"compression": "^1.6.1",
|
"compression": "^1.6.1",
|
||||||
"connect-ratelimit": "0.0.7",
|
|
||||||
"cookie-session": "^1.2.0",
|
"cookie-session": "^1.2.0",
|
||||||
"coupon-code": "^0.4.5",
|
"coupon-code": "^0.4.5",
|
||||||
|
"cross-env": "^4.0.0",
|
||||||
"css-loader": "^0.28.0",
|
"css-loader": "^0.28.0",
|
||||||
"csv-stringify": "^1.0.2",
|
"csv-stringify": "^1.0.2",
|
||||||
"cwait": "~1.0.1",
|
"cwait": "~1.0.1",
|
||||||
"domain-middleware": "~0.1.0",
|
"domain-middleware": "~0.1.0",
|
||||||
"estraverse": "^4.1.1",
|
|
||||||
"express": "~4.14.0",
|
"express": "~4.14.0",
|
||||||
"express-basic-auth": "^1.0.1",
|
"express-basic-auth": "^1.0.1",
|
||||||
"express-csv": "~0.6.0",
|
|
||||||
"express-validator": "^2.18.0",
|
"express-validator": "^2.18.0",
|
||||||
"extract-text-webpack-plugin": "^2.0.0-rc.3",
|
"extract-text-webpack-plugin": "^2.0.0-rc.3",
|
||||||
"file-loader": "^0.10.0",
|
|
||||||
"glob": "^4.3.5",
|
"glob": "^4.3.5",
|
||||||
"got": "^6.1.1",
|
"got": "^6.1.1",
|
||||||
"grunt": "~0.4.1",
|
|
||||||
"grunt-cli": "~0.1.9",
|
|
||||||
"grunt-contrib-clean": "~0.6.0",
|
|
||||||
"grunt-contrib-copy": "~0.6.0",
|
|
||||||
"grunt-contrib-cssmin": "~0.10.0",
|
|
||||||
"grunt-contrib-stylus": "~0.20.0",
|
|
||||||
"grunt-contrib-uglify": "~0.6.0",
|
|
||||||
"grunt-contrib-watch": "~0.6.1",
|
|
||||||
"grunt-hashres": "habitrpg/grunt-hashres#v0.4.2",
|
|
||||||
"gulp": "^3.9.0",
|
"gulp": "^3.9.0",
|
||||||
"gulp-babel": "^6.1.2",
|
"gulp-babel": "^6.1.2",
|
||||||
"gulp-grunt": "^0.5.2",
|
|
||||||
"gulp-imagemin": "^2.4.0",
|
"gulp-imagemin": "^2.4.0",
|
||||||
"gulp-nodemon": "^2.0.4",
|
"gulp-nodemon": "^2.0.4",
|
||||||
"gulp-sourcemaps": "^1.6.0",
|
"gulp-sourcemaps": "^1.6.0",
|
||||||
@@ -75,41 +60,37 @@
|
|||||||
"in-app-purchase": "^1.1.6",
|
"in-app-purchase": "^1.1.6",
|
||||||
"intro.js": "^2.6.0",
|
"intro.js": "^2.6.0",
|
||||||
"jade": "~1.11.0",
|
"jade": "~1.11.0",
|
||||||
"jquery": "^3.1.1",
|
"jquery": ">=3.0.0",
|
||||||
"js2xmlparser": "~1.0.0",
|
"js2xmlparser": "~1.0.0",
|
||||||
"lodash": "^4.17.4",
|
"lodash": "^4.17.4",
|
||||||
"merge-stream": "^1.0.0",
|
"merge-stream": "^1.0.0",
|
||||||
"method-override": "^2.3.5",
|
"method-override": "^2.3.5",
|
||||||
"moment": "^2.13.0",
|
"moment": "^2.13.0",
|
||||||
"moment-recur": "habitrpg/moment-recur#v1.0.6",
|
"moment-recur": "git://github.com/habitrpg/moment-recur.git#f147ef27bbc26ca67638385f3db4a44084c76626",
|
||||||
"mongoose": "~4.8.6",
|
"mongoose": "^4.8.6",
|
||||||
"mongoose-id-autoinc": "~2013.7.14-4",
|
"mongoose-id-autoinc": "~2013.7.14-4",
|
||||||
"morgan": "^1.7.0",
|
"morgan": "^1.7.0",
|
||||||
"nconf": "~0.8.2",
|
"nconf": "~0.8.2",
|
||||||
"nib": "^1.1.0",
|
|
||||||
"node-gcm": "^0.14.4",
|
"node-gcm": "^0.14.4",
|
||||||
"node-sass": "^4.5.0",
|
"node-sass": "^4.5.0",
|
||||||
"nodemailer": "^2.3.2",
|
"nodemailer": "^2.3.2",
|
||||||
"object-path": "^0.9.2",
|
|
||||||
"ora": "^1.1.0",
|
"ora": "^1.1.0",
|
||||||
"pageres": "^4.1.1",
|
"pageres": "^4.1.1",
|
||||||
"passport": "^0.3.2",
|
"passport": "^0.3.2",
|
||||||
"passport-facebook": "^2.0.0",
|
"passport-facebook": "^2.0.0",
|
||||||
"passport-google-oauth20": "1.0.0",
|
"passport-google-oauth20": "1.0.0",
|
||||||
"paypal-ipn": "3.0.0",
|
"paypal-ipn": "3.0.0",
|
||||||
"paypal-rest-sdk": "^1.2.1",
|
"paypal-rest-sdk": "^1.8.1",
|
||||||
|
"popper.js": "^1.13.0",
|
||||||
"postcss-easy-import": "^2.0.0",
|
"postcss-easy-import": "^2.0.0",
|
||||||
"pretty-data": "^0.40.0",
|
|
||||||
"ps-tree": "^1.0.0",
|
"ps-tree": "^1.0.0",
|
||||||
"pug": "^2.0.0-beta.12",
|
"pug": "^2.0.0-beta.12",
|
||||||
"push-notify": "habitrpg/push-notify#v1.2.0",
|
"push-notify": "git://github.com/habitrpg/push-notify.git#6bc2b5fdb1bdc9649b9ec1964d79ca50187fc8a9",
|
||||||
"pusher": "^1.3.0",
|
"pusher": "^1.3.0",
|
||||||
"request": "~2.74.0",
|
"request": "~2.74.0",
|
||||||
"rimraf": "^2.4.3",
|
"rimraf": "^2.4.3",
|
||||||
"run-sequence": "^1.1.4",
|
"run-sequence": "^1.1.4",
|
||||||
"s3-upload-stream": "^1.0.6",
|
|
||||||
"sass-loader": "^6.0.2",
|
"sass-loader": "^6.0.2",
|
||||||
"serve-favicon": "^2.3.0",
|
|
||||||
"shelljs": "^0.7.6",
|
"shelljs": "^0.7.6",
|
||||||
"stripe": "^4.2.0",
|
"stripe": "^4.2.0",
|
||||||
"superagent": "^3.4.3",
|
"superagent": "^3.4.3",
|
||||||
@@ -121,16 +102,14 @@
|
|||||||
"useragent": "^2.1.9",
|
"useragent": "^2.1.9",
|
||||||
"uuid": "^3.0.1",
|
"uuid": "^3.0.1",
|
||||||
"validator": "^4.9.0",
|
"validator": "^4.9.0",
|
||||||
"vinyl-buffer": "^1.0.0",
|
"vue": "^2.5.2",
|
||||||
"vinyl-source-stream": "^1.1.0",
|
"vue-loader": "^13.3.0",
|
||||||
"vue": "^2.1.0",
|
|
||||||
"vue-loader": "^11.0.0",
|
|
||||||
"vue-mugen-scroll": "^0.2.1",
|
"vue-mugen-scroll": "^0.2.1",
|
||||||
"vue-notification": "^1.3.2",
|
"vue-router": "^3.0.0",
|
||||||
"vue-router": "^2.0.0-rc.5",
|
|
||||||
"vue-style-loader": "^3.0.0",
|
"vue-style-loader": "^3.0.0",
|
||||||
"vue-template-compiler": "^2.1.10",
|
"vue-template-compiler": "^2.5.2",
|
||||||
"vuejs-datepicker": "^0.9.4",
|
"vuedraggable": "^2.15.0",
|
||||||
|
"vuejs-datepicker": "git://github.com/habitrpg/vuejs-datepicker.git#5d237615463a84a23dd6f3f77c6ab577d68593ec",
|
||||||
"webpack": "^2.2.1",
|
"webpack": "^2.2.1",
|
||||||
"webpack-merge": "^4.0.0",
|
"webpack-merge": "^4.0.0",
|
||||||
"winston": "^2.1.0",
|
"winston": "^2.1.0",
|
||||||
@@ -140,11 +119,11 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^6.9.1",
|
"node": "^6.9.1",
|
||||||
"npm": "^4.0.2"
|
"npm": "^5.0.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"lint": "eslint --ext .js,.vue .",
|
"lint": "eslint --ext .js,.vue .",
|
||||||
"test": "npm run lint && gulp test && npm run client:unit && gulp apidoc",
|
"test": "npm run lint && gulp test && gulp apidoc",
|
||||||
"test:build": "gulp test:prepare:build",
|
"test:build": "gulp test:prepare:build",
|
||||||
"test:api-v3": "gulp test:api-v3",
|
"test:api-v3": "gulp test:api-v3",
|
||||||
"test:api-v3:unit": "gulp test:api-v3:unit",
|
"test:api-v3:unit": "gulp test:api-v3:unit",
|
||||||
@@ -153,22 +132,17 @@
|
|||||||
"test:sanity": "istanbul cover --dir coverage/sanity --report lcovonly node_modules/mocha/bin/_mocha -- test/sanity --recursive",
|
"test:sanity": "istanbul cover --dir coverage/sanity --report lcovonly node_modules/mocha/bin/_mocha -- test/sanity --recursive",
|
||||||
"test:common": "istanbul cover --dir coverage/common --report lcovonly node_modules/mocha/bin/_mocha -- test/common --recursive",
|
"test:common": "istanbul cover --dir coverage/common --report lcovonly node_modules/mocha/bin/_mocha -- test/common --recursive",
|
||||||
"test:content": "istanbul cover --dir coverage/content --report lcovonly node_modules/mocha/bin/_mocha -- test/content --recursive",
|
"test:content": "istanbul cover --dir coverage/content --report lcovonly node_modules/mocha/bin/_mocha -- test/content --recursive",
|
||||||
"test:karma": "karma start test/client-old/spec/karma.conf.js --single-run",
|
|
||||||
"test:karma:watch": "karma start test/client-old/spec/karma.conf.js",
|
|
||||||
"test:prepare:webdriver": "webdriver-manager update",
|
|
||||||
"test:e2e:webdriver": "webdriver-manager start",
|
|
||||||
"test:e2e": "protractor test/client-old/e2e/protractor.conf.js",
|
|
||||||
"test:nodemon": "gulp test:nodemon",
|
"test:nodemon": "gulp test:nodemon",
|
||||||
"coverage": "COVERAGE=true mocha --require register-handlers.js --reporter html-cov > coverage.html; open coverage.html",
|
"coverage": "COVERAGE=true mocha --require register-handlers.js --reporter html-cov > coverage.html; open coverage.html",
|
||||||
"sprites": "gulp sprites:compile",
|
"sprites": "gulp sprites:compile",
|
||||||
"client:dev": "gulp bootstrap && node webpack/dev-server.js",
|
"client:dev": "node webpack/dev-server.js",
|
||||||
"client:build": "gulp bootstrap && node webpack/build.js",
|
"client:build": "gulp build:client",
|
||||||
"client:unit": "cross-env NODE_ENV=test karma start test/client/unit/karma.conf.js --single-run",
|
"client:unit": "cross-env NODE_ENV=test karma start test/client/unit/karma.conf.js --single-run",
|
||||||
"client:unit:watch": "cross-env NODE_ENV=test karma start test/client/unit/karma.conf.js",
|
"client:unit:watch": "cross-env NODE_ENV=test karma start test/client/unit/karma.conf.js",
|
||||||
"client:e2e": "node test/client/e2e/runner.js",
|
"client:e2e": "node test/client/e2e/runner.js",
|
||||||
"client:test": "npm run client:unit && npm run client:e2e",
|
"client:test": "npm run client:unit && npm run client:e2e",
|
||||||
"start": "gulp run:dev",
|
"start": "gulp nodemon",
|
||||||
"postinstall": "bower --config.interactive=false install -f && gulp build && npm run client:build",
|
"postinstall": "gulp build",
|
||||||
"apidoc": "gulp apidoc"
|
"apidoc": "gulp apidoc"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -179,22 +153,17 @@
|
|||||||
"chromedriver": "^2.27.2",
|
"chromedriver": "^2.27.2",
|
||||||
"connect-history-api-fallback": "^1.1.0",
|
"connect-history-api-fallback": "^1.1.0",
|
||||||
"coveralls": "^2.11.2",
|
"coveralls": "^2.11.2",
|
||||||
"cross-env": "^4.0.0",
|
|
||||||
"cross-spawn": "^5.0.1",
|
"cross-spawn": "^5.0.1",
|
||||||
"csv": "~0.3.6",
|
"csv": "~0.3.6",
|
||||||
"deep-diff": "~0.1.4",
|
|
||||||
"eslint": "^3.0.0",
|
"eslint": "^3.0.0",
|
||||||
"eslint-config-habitrpg": "^3.0.0",
|
"eslint-config-habitrpg": "^3.0.0",
|
||||||
"eslint-friendly-formatter": "^2.0.5",
|
"eslint-friendly-formatter": "^2.0.5",
|
||||||
"eslint-loader": "^1.3.0",
|
"eslint-loader": "^1.3.0",
|
||||||
"eslint-plugin-html": "^2.0.0",
|
"eslint-plugin-html": "^2.0.0",
|
||||||
"eslint-plugin-mocha": "^4.7.0",
|
"eslint-plugin-mocha": "^4.7.0",
|
||||||
"event-stream": "^3.2.2",
|
|
||||||
"eventsource-polyfill": "^0.9.6",
|
"eventsource-polyfill": "^0.9.6",
|
||||||
"expect.js": "~0.2.0",
|
"expect.js": "~0.2.0",
|
||||||
"grunt-karma": "~0.12.1",
|
|
||||||
"http-proxy-middleware": "^0.17.0",
|
"http-proxy-middleware": "^0.17.0",
|
||||||
"inject-loader": "^3.0.0-beta4",
|
|
||||||
"istanbul": "^1.1.0-alpha.1",
|
"istanbul": "^1.1.0-alpha.1",
|
||||||
"karma": "^1.3.0",
|
"karma": "^1.3.0",
|
||||||
"karma-babel-preprocessor": "^6.0.1",
|
"karma-babel-preprocessor": "^6.0.1",
|
||||||
@@ -209,23 +178,17 @@
|
|||||||
"karma-spec-reporter": "0.0.24",
|
"karma-spec-reporter": "0.0.24",
|
||||||
"karma-webpack": "^2.0.2",
|
"karma-webpack": "^2.0.2",
|
||||||
"lcov-result-merger": "^1.0.2",
|
"lcov-result-merger": "^1.0.2",
|
||||||
"lolex": "^1.4.0",
|
|
||||||
"mocha": "^3.2.0",
|
"mocha": "^3.2.0",
|
||||||
"mongodb": "^2.0.46",
|
"mongodb": "^2.2.33",
|
||||||
"mongoskin": "~2.1.0",
|
"mongoskin": "~2.1.0",
|
||||||
"monk": "^4.0.0",
|
"monk": "^4.0.0",
|
||||||
"nightwatch": "^0.9.12",
|
"nightwatch": "^0.9.12",
|
||||||
"phantomjs-prebuilt": "^2.1.12",
|
"phantomjs-prebuilt": "^2.1.12",
|
||||||
"protractor": "^3.1.1",
|
|
||||||
"raw-loader": "^0.5.1",
|
|
||||||
"require-again": "^2.0.0",
|
"require-again": "^2.0.0",
|
||||||
"rewire": "^2.3.3",
|
|
||||||
"selenium-server": "^3.0.1",
|
"selenium-server": "^3.0.1",
|
||||||
"sinon": "^1.17.2",
|
"sinon": "^4.2.2",
|
||||||
"sinon-chai": "^2.8.0",
|
"sinon-chai": "^2.8.0",
|
||||||
"sinon-stub-promise": "^4.0.0",
|
"sinon-stub-promise": "^4.0.0",
|
||||||
"superagent-defaults": "^0.1.13",
|
|
||||||
"vinyl-transform": "^1.0.0",
|
|
||||||
"webpack-bundle-analyzer": "^2.2.1",
|
"webpack-bundle-analyzer": "^2.2.1",
|
||||||
"webpack-dev-middleware": "^1.10.0",
|
"webpack-dev-middleware": "^1.10.0",
|
||||||
"webpack-hot-middleware": "^2.6.1"
|
"webpack-hot-middleware": "^2.6.1"
|
||||||
|
|||||||
@@ -48,8 +48,10 @@ describe('GET /challenges/:challengeId', () => {
|
|||||||
});
|
});
|
||||||
expect(chal.group).to.eql({
|
expect(chal.group).to.eql({
|
||||||
_id: group._id,
|
_id: group._id,
|
||||||
|
categories: [],
|
||||||
id: group.id,
|
id: group.id,
|
||||||
name: group.name,
|
name: group.name,
|
||||||
|
summary: group.name,
|
||||||
type: group.type,
|
type: group.type,
|
||||||
privacy: group.privacy,
|
privacy: group.privacy,
|
||||||
leader: groupLeader.id,
|
leader: groupLeader.id,
|
||||||
@@ -100,8 +102,10 @@ describe('GET /challenges/:challengeId', () => {
|
|||||||
});
|
});
|
||||||
expect(chal.group).to.eql({
|
expect(chal.group).to.eql({
|
||||||
_id: group._id,
|
_id: group._id,
|
||||||
|
categories: [],
|
||||||
id: group.id,
|
id: group.id,
|
||||||
name: group.name,
|
name: group.name,
|
||||||
|
summary: group.name,
|
||||||
type: group.type,
|
type: group.type,
|
||||||
privacy: group.privacy,
|
privacy: group.privacy,
|
||||||
leader: groupLeader.id,
|
leader: groupLeader.id,
|
||||||
@@ -153,7 +157,9 @@ describe('GET /challenges/:challengeId', () => {
|
|||||||
expect(chal.group).to.eql({
|
expect(chal.group).to.eql({
|
||||||
_id: group._id,
|
_id: group._id,
|
||||||
id: group.id,
|
id: group.id,
|
||||||
|
categories: [],
|
||||||
name: group.name,
|
name: group.name,
|
||||||
|
summary: group.name,
|
||||||
type: group.type,
|
type: group.type,
|
||||||
privacy: group.privacy,
|
privacy: group.privacy,
|
||||||
leader: groupLeader.id,
|
leader: groupLeader.id,
|
||||||
|
|||||||
@@ -64,11 +64,11 @@ describe('GET /challenges/:challengeId/export/csv', () => {
|
|||||||
let sortedMembers = _.sortBy([members[0], members[1], members[2], groupLeader], '_id');
|
let sortedMembers = _.sortBy([members[0], members[1], members[2], groupLeader], '_id');
|
||||||
let splitRes = res.split('\n');
|
let splitRes = res.split('\n');
|
||||||
|
|
||||||
expect(splitRes[0]).to.equal('UUID,name,Task,Value,Notes,Task,Value,Notes');
|
expect(splitRes[0]).to.equal('UUID,name,Task,Value,Notes,Streak,Task,Value,Notes,Streak');
|
||||||
expect(splitRes[1]).to.equal(`${sortedMembers[0]._id},${sortedMembers[0].profile.name},habit:Task 1,0,,todo:Task 2,0,`);
|
expect(splitRes[1]).to.equal(`${sortedMembers[0]._id},${sortedMembers[0].profile.name},habit:Task 1,0,,0,todo:Task 2,0,,0`);
|
||||||
expect(splitRes[2]).to.equal(`${sortedMembers[1]._id},${sortedMembers[1].profile.name},habit:Task 1,0,,todo:Task 2,0,`);
|
expect(splitRes[2]).to.equal(`${sortedMembers[1]._id},${sortedMembers[1].profile.name},habit:Task 1,0,,0,todo:Task 2,0,,0`);
|
||||||
expect(splitRes[3]).to.equal(`${sortedMembers[2]._id},${sortedMembers[2].profile.name},habit:Task 1,0,,todo:Task 2,0,`);
|
expect(splitRes[3]).to.equal(`${sortedMembers[2]._id},${sortedMembers[2].profile.name},habit:Task 1,0,,0,todo:Task 2,0,,0`);
|
||||||
expect(splitRes[4]).to.equal(`${sortedMembers[3]._id},${sortedMembers[3].profile.name},habit:Task 1,0,,todo:Task 2,0,`);
|
expect(splitRes[4]).to.equal(`${sortedMembers[3]._id},${sortedMembers[3].profile.name},habit:Task 1,0,,0,todo:Task 2,0,,0`);
|
||||||
expect(splitRes[5]).to.equal('');
|
expect(splitRes[5]).to.equal('');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -142,4 +142,25 @@ describe('GET /challenges/:challengeId/members', () => {
|
|||||||
let resIds = res.concat(res2).map(member => member._id);
|
let resIds = res.concat(res2).map(member => member._id);
|
||||||
expect(resIds).to.eql(expectedIds.sort());
|
expect(resIds).to.eql(expectedIds.sort());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('supports using req.query.search to get search members', async () => {
|
||||||
|
let group = await generateGroup(user, {type: 'party', name: generateUUID()});
|
||||||
|
let challenge = await generateChallenge(user, group);
|
||||||
|
|
||||||
|
let usersToGenerate = [];
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
usersToGenerate.push(generateUser({
|
||||||
|
challenges: [challenge._id],
|
||||||
|
'profile.name': `${i}profilename`,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
let generatedUsers = await Promise.all(usersToGenerate);
|
||||||
|
let profileNames = generatedUsers.map(generatedUser => generatedUser.profile.name);
|
||||||
|
|
||||||
|
let firstProfileName = profileNames[0];
|
||||||
|
let nameToSearch = firstProfileName.substring(0, 4);
|
||||||
|
|
||||||
|
let response = await user.get(`/challenges/${challenge._id}/members?search=${nameToSearch}`);
|
||||||
|
expect(response[0].profile.name).to.eql(firstProfileName);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -95,13 +95,23 @@ describe('GET /challenges/:challengeId/members/:memberId', () => {
|
|||||||
expect(memberProgress.tasks[0].challenge.taskId).to.equal(chalTasks[0]._id);
|
expect(memberProgress.tasks[0].challenge.taskId).to.equal(chalTasks[0]._id);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns the tasks without the tags', async () => {
|
it('returns the tasks without the tags and checklist', async () => {
|
||||||
let group = await generateGroup(user, {type: 'party', name: generateUUID()});
|
let group = await generateGroup(user, {type: 'party', name: generateUUID()});
|
||||||
let challenge = await generateChallenge(user, group);
|
let challenge = await generateChallenge(user, group);
|
||||||
let taskText = 'Test Text';
|
let taskText = 'Test Text';
|
||||||
await user.post(`/tasks/challenge/${challenge._id}`, [{type: 'habit', text: taskText}]);
|
await user.post(`/tasks/challenge/${challenge._id}`, [{
|
||||||
|
type: 'todo',
|
||||||
|
text: taskText,
|
||||||
|
checklist: [
|
||||||
|
{
|
||||||
|
_id: 123,
|
||||||
|
text: 'test',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}]);
|
||||||
|
|
||||||
let memberProgress = await user.get(`/challenges/${challenge._id}/members/${user._id}`);
|
let memberProgress = await user.get(`/challenges/${challenge._id}/members/${user._id}`);
|
||||||
expect(memberProgress.tasks[0]).not.to.have.key('tags');
|
expect(memberProgress.tasks[0]).not.to.have.key('tags');
|
||||||
|
expect(memberProgress.tasks[0].checklist).to.eql([]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import {
|
|||||||
createAndPopulateGroup,
|
createAndPopulateGroup,
|
||||||
translate as t,
|
translate as t,
|
||||||
} from '../../../../helpers/api-v3-integration.helper';
|
} from '../../../../helpers/api-v3-integration.helper';
|
||||||
|
import { TAVERN_ID } from '../../../../../website/common/script/constants';
|
||||||
|
|
||||||
describe('GET challenges/groups/:groupId', () => {
|
describe('GET challenges/groups/:groupId', () => {
|
||||||
context('Public Guild', () => {
|
context('Public Guild', () => {
|
||||||
@@ -181,4 +182,123 @@ describe('GET challenges/groups/:groupId', () => {
|
|||||||
expect(foundChallengeIndex).to.eql(1);
|
expect(foundChallengeIndex).to.eql(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
context('Party', () => {
|
||||||
|
let party, user, nonMember, challenge, challenge2;
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
let { group, groupLeader } = await createAndPopulateGroup({
|
||||||
|
groupDetails: {
|
||||||
|
name: 'TestParty',
|
||||||
|
type: 'party',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
party = group;
|
||||||
|
user = groupLeader;
|
||||||
|
|
||||||
|
nonMember = await generateUser();
|
||||||
|
|
||||||
|
challenge = await generateChallenge(user, group);
|
||||||
|
challenge2 = await generateChallenge(user, group);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should prevent non-member from seeing challenges', async () => {
|
||||||
|
await expect(nonMember.get(`/challenges/groups/${party._id}`))
|
||||||
|
.to.eventually.be.rejected.and.eql({
|
||||||
|
code: 404,
|
||||||
|
error: 'NotFound',
|
||||||
|
message: t('groupNotFound'),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return group challenges for member with populated leader', async () => {
|
||||||
|
let challenges = await user.get(`/challenges/groups/${party._id}`);
|
||||||
|
|
||||||
|
let foundChallenge1 = _.find(challenges, { _id: challenge._id });
|
||||||
|
expect(foundChallenge1).to.exist;
|
||||||
|
expect(foundChallenge1.leader).to.eql({
|
||||||
|
_id: party.leader._id,
|
||||||
|
id: party.leader._id,
|
||||||
|
profile: {name: user.profile.name},
|
||||||
|
});
|
||||||
|
let foundChallenge2 = _.find(challenges, { _id: challenge2._id });
|
||||||
|
expect(foundChallenge2).to.exist;
|
||||||
|
expect(foundChallenge2.leader).to.eql({
|
||||||
|
_id: party.leader._id,
|
||||||
|
id: party.leader._id,
|
||||||
|
profile: {name: user.profile.name},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return group challenges for member using ID "party"', async () => {
|
||||||
|
let challenges = await user.get('/challenges/groups/party');
|
||||||
|
|
||||||
|
let foundChallenge1 = _.find(challenges, { _id: challenge._id });
|
||||||
|
expect(foundChallenge1).to.exist;
|
||||||
|
expect(foundChallenge1.leader).to.eql({
|
||||||
|
_id: party.leader._id,
|
||||||
|
id: party.leader._id,
|
||||||
|
profile: {name: user.profile.name},
|
||||||
|
});
|
||||||
|
let foundChallenge2 = _.find(challenges, { _id: challenge2._id });
|
||||||
|
expect(foundChallenge2).to.exist;
|
||||||
|
expect(foundChallenge2.leader).to.eql({
|
||||||
|
_id: party.leader._id,
|
||||||
|
id: party.leader._id,
|
||||||
|
profile: {name: user.profile.name},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
context('Tavern', () => {
|
||||||
|
let tavern, user, challenge, challenge2;
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
user = await generateUser();
|
||||||
|
await user.update({balance: 0.5});
|
||||||
|
tavern = await user.get(`/groups/${TAVERN_ID}`);
|
||||||
|
|
||||||
|
challenge = await generateChallenge(user, tavern, {prize: 1});
|
||||||
|
challenge2 = await generateChallenge(user, tavern, {prize: 1});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return tavern challenges with populated leader', async () => {
|
||||||
|
let challenges = await user.get(`/challenges/groups/${TAVERN_ID}`);
|
||||||
|
|
||||||
|
let foundChallenge1 = _.find(challenges, { _id: challenge._id });
|
||||||
|
expect(foundChallenge1).to.exist;
|
||||||
|
expect(foundChallenge1.leader).to.eql({
|
||||||
|
_id: user._id,
|
||||||
|
id: user._id,
|
||||||
|
profile: {name: user.profile.name},
|
||||||
|
});
|
||||||
|
let foundChallenge2 = _.find(challenges, { _id: challenge2._id });
|
||||||
|
expect(foundChallenge2).to.exist;
|
||||||
|
expect(foundChallenge2.leader).to.eql({
|
||||||
|
_id: user._id,
|
||||||
|
id: user._id,
|
||||||
|
profile: {name: user.profile.name},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return tavern challenges using ID "habitrpg', async () => {
|
||||||
|
let challenges = await user.get('/challenges/groups/habitrpg');
|
||||||
|
|
||||||
|
let foundChallenge1 = _.find(challenges, { _id: challenge._id });
|
||||||
|
expect(foundChallenge1).to.exist;
|
||||||
|
expect(foundChallenge1.leader).to.eql({
|
||||||
|
_id: user._id,
|
||||||
|
id: user._id,
|
||||||
|
profile: {name: user.profile.name},
|
||||||
|
});
|
||||||
|
let foundChallenge2 = _.find(challenges, { _id: challenge2._id });
|
||||||
|
expect(foundChallenge2).to.exist;
|
||||||
|
expect(foundChallenge2.leader).to.eql({
|
||||||
|
_id: user._id,
|
||||||
|
id: user._id,
|
||||||
|
profile: {name: user.profile.name},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -41,10 +41,12 @@ describe('GET challenges/user', () => {
|
|||||||
});
|
});
|
||||||
expect(foundChallenge.group).to.eql({
|
expect(foundChallenge.group).to.eql({
|
||||||
_id: publicGuild._id,
|
_id: publicGuild._id,
|
||||||
|
categories: [],
|
||||||
id: publicGuild._id,
|
id: publicGuild._id,
|
||||||
type: publicGuild.type,
|
type: publicGuild.type,
|
||||||
privacy: publicGuild.privacy,
|
privacy: publicGuild.privacy,
|
||||||
name: publicGuild.name,
|
name: publicGuild.name,
|
||||||
|
summary: publicGuild.name,
|
||||||
leader: publicGuild.leader._id,
|
leader: publicGuild.leader._id,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -61,10 +63,12 @@ describe('GET challenges/user', () => {
|
|||||||
});
|
});
|
||||||
expect(foundChallenge1.group).to.eql({
|
expect(foundChallenge1.group).to.eql({
|
||||||
_id: publicGuild._id,
|
_id: publicGuild._id,
|
||||||
|
categories: [],
|
||||||
id: publicGuild._id,
|
id: publicGuild._id,
|
||||||
type: publicGuild.type,
|
type: publicGuild.type,
|
||||||
privacy: publicGuild.privacy,
|
privacy: publicGuild.privacy,
|
||||||
name: publicGuild.name,
|
name: publicGuild.name,
|
||||||
|
summary: publicGuild.name,
|
||||||
leader: publicGuild.leader._id,
|
leader: publicGuild.leader._id,
|
||||||
});
|
});
|
||||||
let foundChallenge2 = _.find(challenges, { _id: challenge2._id });
|
let foundChallenge2 = _.find(challenges, { _id: challenge2._id });
|
||||||
@@ -76,10 +80,12 @@ describe('GET challenges/user', () => {
|
|||||||
});
|
});
|
||||||
expect(foundChallenge2.group).to.eql({
|
expect(foundChallenge2.group).to.eql({
|
||||||
_id: publicGuild._id,
|
_id: publicGuild._id,
|
||||||
|
categories: [],
|
||||||
id: publicGuild._id,
|
id: publicGuild._id,
|
||||||
type: publicGuild.type,
|
type: publicGuild.type,
|
||||||
privacy: publicGuild.privacy,
|
privacy: publicGuild.privacy,
|
||||||
name: publicGuild.name,
|
name: publicGuild.name,
|
||||||
|
summary: publicGuild.name,
|
||||||
leader: publicGuild.leader._id,
|
leader: publicGuild.leader._id,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -96,10 +102,12 @@ describe('GET challenges/user', () => {
|
|||||||
});
|
});
|
||||||
expect(foundChallenge1.group).to.eql({
|
expect(foundChallenge1.group).to.eql({
|
||||||
_id: publicGuild._id,
|
_id: publicGuild._id,
|
||||||
|
categories: [],
|
||||||
id: publicGuild._id,
|
id: publicGuild._id,
|
||||||
type: publicGuild.type,
|
type: publicGuild.type,
|
||||||
privacy: publicGuild.privacy,
|
privacy: publicGuild.privacy,
|
||||||
name: publicGuild.name,
|
name: publicGuild.name,
|
||||||
|
summary: publicGuild.name,
|
||||||
leader: publicGuild.leader._id,
|
leader: publicGuild.leader._id,
|
||||||
});
|
});
|
||||||
let foundChallenge2 = _.find(challenges, { _id: challenge2._id });
|
let foundChallenge2 = _.find(challenges, { _id: challenge2._id });
|
||||||
@@ -111,14 +119,26 @@ describe('GET challenges/user', () => {
|
|||||||
});
|
});
|
||||||
expect(foundChallenge2.group).to.eql({
|
expect(foundChallenge2.group).to.eql({
|
||||||
_id: publicGuild._id,
|
_id: publicGuild._id,
|
||||||
|
categories: [],
|
||||||
id: publicGuild._id,
|
id: publicGuild._id,
|
||||||
type: publicGuild.type,
|
type: publicGuild.type,
|
||||||
privacy: publicGuild.privacy,
|
privacy: publicGuild.privacy,
|
||||||
name: publicGuild.name,
|
name: publicGuild.name,
|
||||||
|
summary: publicGuild.name,
|
||||||
leader: publicGuild.leader._id,
|
leader: publicGuild.leader._id,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should return not return challenges in user groups if we send member true param', async () => {
|
||||||
|
let challenges = await member.get(`/challenges/user?member=${true}`);
|
||||||
|
|
||||||
|
let foundChallenge1 = _.find(challenges, { _id: challenge._id });
|
||||||
|
expect(foundChallenge1).to.not.exist;
|
||||||
|
|
||||||
|
let foundChallenge2 = _.find(challenges, { _id: challenge2._id });
|
||||||
|
expect(foundChallenge2).to.not.exist;
|
||||||
|
});
|
||||||
|
|
||||||
it('should return newest challenges first', async () => {
|
it('should return newest challenges first', async () => {
|
||||||
let challenges = await user.get('/challenges/user');
|
let challenges = await user.get('/challenges/user');
|
||||||
|
|
||||||
@@ -137,6 +157,7 @@ describe('GET challenges/user', () => {
|
|||||||
let { group, groupLeader } = await createAndPopulateGroup({
|
let { group, groupLeader } = await createAndPopulateGroup({
|
||||||
groupDetails: {
|
groupDetails: {
|
||||||
name: 'TestPrivateGuild',
|
name: 'TestPrivateGuild',
|
||||||
|
summary: 'summary for TestPrivateGuild',
|
||||||
type: 'guild',
|
type: 'guild',
|
||||||
privacy: 'private',
|
privacy: 'private',
|
||||||
},
|
},
|
||||||
@@ -158,6 +179,7 @@ describe('GET challenges/user', () => {
|
|||||||
let { group, groupLeader } = await createAndPopulateGroup({
|
let { group, groupLeader } = await createAndPopulateGroup({
|
||||||
groupDetails: {
|
groupDetails: {
|
||||||
name: 'TestGuild',
|
name: 'TestGuild',
|
||||||
|
summary: 'summary for TestGuild',
|
||||||
type: 'guild',
|
type: 'guild',
|
||||||
privacy: 'public',
|
privacy: 'public',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -101,19 +101,21 @@ describe('POST /challenges/:challengeId/join', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('syncs challenge tasks to joining user', async () => {
|
it('syncs challenge tasks to joining user', async () => {
|
||||||
let taskText = 'A challenge task text';
|
const taskText = 'A challenge task text';
|
||||||
|
|
||||||
await groupLeader.post(`/tasks/challenge/${challenge._id}`, [
|
await groupLeader.post(`/tasks/challenge/${challenge._id}`, [
|
||||||
{type: 'habit', text: taskText},
|
{type: 'daily', text: taskText},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
await authorizedUser.post(`/challenges/${challenge._id}/join`);
|
await authorizedUser.post(`/challenges/${challenge._id}/join`);
|
||||||
let tasks = await authorizedUser.get('/tasks/user');
|
|
||||||
let tasksTexts = tasks.map((task) => {
|
const tasks = await authorizedUser.get('/tasks/user');
|
||||||
return task.text;
|
const syncedTask = tasks.find((task) => {
|
||||||
|
return task.text === taskText;
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(tasksTexts).to.include(taskText);
|
expect(syncedTask.text).to.eql(taskText);
|
||||||
|
expect(syncedTask.isDue).to.exist;
|
||||||
|
expect(syncedTask.nextDue).to.exist;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('adds challenge tag to user tags', async () => {
|
it('adds challenge tag to user tags', async () => {
|
||||||
|
|||||||
@@ -149,13 +149,19 @@ describe('POST /challenges/:challengeId/winner/:winnerId', () => {
|
|||||||
|
|
||||||
await sleep(0.5);
|
await sleep(0.5);
|
||||||
|
|
||||||
let tasks = await winningUser.get('/tasks/user');
|
const tasks = await winningUser.get('/tasks/user');
|
||||||
let testTask = _.find(tasks, (task) => {
|
const testTask = _.find(tasks, (task) => {
|
||||||
return task.text === taskText;
|
return task.text === taskText;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const updatedUser = await winningUser.sync();
|
||||||
|
const challengeTag = updatedUser.tags.find(tags => {
|
||||||
|
return tags.id === challenge._id;
|
||||||
|
});
|
||||||
|
|
||||||
expect(testTask.challenge.broken).to.eql('CHALLENGE_CLOSED');
|
expect(testTask.challenge.broken).to.eql('CHALLENGE_CLOSED');
|
||||||
expect(testTask.challenge.winner).to.eql(winningUser.profile.name);
|
expect(testTask.challenge.winner).to.eql(winningUser.profile.name);
|
||||||
|
expect(challengeTag.challenge).to.eql('false');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import {
|
||||||
|
generateUser,
|
||||||
|
generateGroup,
|
||||||
|
} from '../../../../helpers/api-v3-integration.helper';
|
||||||
|
|
||||||
|
describe('POST /challenges/:challengeId/clone', () => {
|
||||||
|
it('clones a challenge', async () => {
|
||||||
|
const user = await generateUser({balance: 10});
|
||||||
|
const group = await generateGroup(user);
|
||||||
|
|
||||||
|
const name = 'Test Challenge';
|
||||||
|
const shortName = 'TC Label';
|
||||||
|
const description = 'Test Description';
|
||||||
|
const prize = 1;
|
||||||
|
|
||||||
|
const challenge = await user.post('/challenges', {
|
||||||
|
group: group._id,
|
||||||
|
name,
|
||||||
|
shortName,
|
||||||
|
description,
|
||||||
|
prize,
|
||||||
|
});
|
||||||
|
const challengeTask = await user.post(`/tasks/challenge/${challenge._id}`, {
|
||||||
|
text: 'test habit',
|
||||||
|
type: 'habit',
|
||||||
|
up: false,
|
||||||
|
down: true,
|
||||||
|
notes: 1976,
|
||||||
|
});
|
||||||
|
|
||||||
|
const cloneChallengeResponse = await user.post(`/challenges/${challenge._id}/clone`, {
|
||||||
|
group: group._id,
|
||||||
|
name: `${name} cloned`,
|
||||||
|
shortName,
|
||||||
|
description,
|
||||||
|
prize,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(cloneChallengeResponse.clonedTasks[0].text).to.eql(challengeTask.text);
|
||||||
|
expect(cloneChallengeResponse.clonedTasks[0]._id).to.not.eql(challengeTask._id);
|
||||||
|
expect(cloneChallengeResponse.clonedTasks[0].challenge.id).to.eql(cloneChallengeResponse.clonedChallenge._id);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
createAndPopulateGroup,
|
createAndPopulateGroup,
|
||||||
|
generateUser,
|
||||||
translate as t,
|
translate as t,
|
||||||
sleep,
|
sleep,
|
||||||
server,
|
server,
|
||||||
@@ -12,6 +13,7 @@ import {
|
|||||||
import { v4 as generateUUID } from 'uuid';
|
import { v4 as generateUUID } from 'uuid';
|
||||||
import { getMatchesByWordArray, removePunctuationFromString } from '../../../../../website/server/libs/stringUtils';
|
import { getMatchesByWordArray, removePunctuationFromString } from '../../../../../website/server/libs/stringUtils';
|
||||||
import bannedWords from '../../../../../website/server/libs/bannedWords';
|
import bannedWords from '../../../../../website/server/libs/bannedWords';
|
||||||
|
import guildsAllowingBannedWords from '../../../../../website/server/libs/guildsAllowingBannedWords';
|
||||||
import * as email from '../../../../../website/server/libs/email';
|
import * as email from '../../../../../website/server/libs/email';
|
||||||
import { IncomingWebhook } from '@slack/client';
|
import { IncomingWebhook } from '@slack/client';
|
||||||
import nconf from 'nconf';
|
import nconf from 'nconf';
|
||||||
@@ -96,6 +98,24 @@ describe('POST /chat', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('returns an error when chat message contains a banned word in a public guild', async () => {
|
||||||
|
let { 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 () => {
|
it('errors when word is part of a phrase', async () => {
|
||||||
let wordInPhrase = `phrase ${testBannedWordMessage} end`;
|
let wordInPhrase = `phrase ${testBannedWordMessage} end`;
|
||||||
await expect(user.post('/groups/habitrpg/chat', { message: wordInPhrase}))
|
await expect(user.post('/groups/habitrpg/chat', { message: wordInPhrase}))
|
||||||
@@ -161,7 +181,7 @@ 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', async () => {
|
it('does not error when sending a chat message containing a banned word to a public guild in which banned words are allowed', async () => {
|
||||||
let { group, members } = await createAndPopulateGroup({
|
let { group, members } = await createAndPopulateGroup({
|
||||||
groupDetails: {
|
groupDetails: {
|
||||||
name: 'public guild',
|
name: 'public guild',
|
||||||
@@ -171,6 +191,8 @@ describe('POST /chat', () => {
|
|||||||
members: 1,
|
members: 1,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
guildsAllowingBannedWords[group._id] = true;
|
||||||
|
|
||||||
let message = await members[0].post(`/groups/${group._id}/chat`, { message: testBannedWordMessage});
|
let message = await members[0].post(`/groups/${group._id}/chat`, { message: testBannedWordMessage});
|
||||||
|
|
||||||
expect(message.message.id).to.exist;
|
expect(message.message.id).to.exist;
|
||||||
@@ -224,7 +246,7 @@ describe('POST /chat', () => {
|
|||||||
color: 'danger',
|
color: 'danger',
|
||||||
author_name: `${user.profile.name} - ${user.auth.local.email} - ${user._id}`,
|
author_name: `${user.profile.name} - ${user.auth.local.email} - ${user._id}`,
|
||||||
title: 'Slur in Test Guild',
|
title: 'Slur in Test Guild',
|
||||||
title_link: `${BASE_URL}/#/options/groups/guilds/${groupWithChat.id}`,
|
title_link: `${BASE_URL}/groups/guild/${groupWithChat.id}`,
|
||||||
text: testSlurMessage,
|
text: testSlurMessage,
|
||||||
// footer: sandbox.match(/<.*?groupId=group-id&chatId=chat-id\|Flag this message>/),
|
// footer: sandbox.match(/<.*?groupId=group-id&chatId=chat-id\|Flag this message>/),
|
||||||
mrkdwn_in: [
|
mrkdwn_in: [
|
||||||
@@ -342,6 +364,24 @@ describe('POST /chat', () => {
|
|||||||
expect(message.message.id).to.exist;
|
expect(message.message.id).to.exist;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('adds backer info to chat', async () => {
|
||||||
|
const backerInfo = {
|
||||||
|
npc: 'Town Crier',
|
||||||
|
tier: 800,
|
||||||
|
tokensApplied: true,
|
||||||
|
};
|
||||||
|
const backer = await generateUser({
|
||||||
|
backer: backerInfo,
|
||||||
|
});
|
||||||
|
|
||||||
|
const message = await backer.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage});
|
||||||
|
const messageBackerInfo = message.message.backer;
|
||||||
|
|
||||||
|
expect(messageBackerInfo.npc).to.equal(backerInfo.npc);
|
||||||
|
expect(messageBackerInfo.tier).to.equal(backerInfo.tier);
|
||||||
|
expect(messageBackerInfo.tokensApplied).to.equal(backerInfo.tokensApplied);
|
||||||
|
});
|
||||||
|
|
||||||
it('sends group chat received webhooks', async () => {
|
it('sends group chat received webhooks', async () => {
|
||||||
let userUuid = generateUUID();
|
let userUuid = generateUUID();
|
||||||
let memberUuid = generateUUID();
|
let memberUuid = generateUUID();
|
||||||
@@ -386,6 +426,9 @@ describe('POST /chat', () => {
|
|||||||
|
|
||||||
expect(message.message.id).to.exist;
|
expect(message.message.id).to.exist;
|
||||||
expect(memberWithNotification.newMessages[`${groupWithChat._id}`]).to.exist;
|
expect(memberWithNotification.newMessages[`${groupWithChat._id}`]).to.exist;
|
||||||
|
expect(memberWithNotification.notifications.find(n => {
|
||||||
|
return n.type === 'NEW_CHAT_MESSAGE' && n.data.group.id === groupWithChat._id;
|
||||||
|
})).to.exist;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('notifies other users of new messages for a party', async () => {
|
it('notifies other users of new messages for a party', async () => {
|
||||||
@@ -403,6 +446,9 @@ describe('POST /chat', () => {
|
|||||||
|
|
||||||
expect(message.message.id).to.exist;
|
expect(message.message.id).to.exist;
|
||||||
expect(memberWithNotification.newMessages[`${group._id}`]).to.exist;
|
expect(memberWithNotification.newMessages[`${group._id}`]).to.exist;
|
||||||
|
expect(memberWithNotification.notifications.find(n => {
|
||||||
|
return n.type === 'NEW_CHAT_MESSAGE' && n.data.group.id === group._id;
|
||||||
|
})).to.exist;
|
||||||
});
|
});
|
||||||
|
|
||||||
context('Spam prevention', () => {
|
context('Spam prevention', () => {
|
||||||
|
|||||||
@@ -24,10 +24,13 @@ describe('POST /groups/:id/chat/seen', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('clears new messages for a guild', async () => {
|
it('clears new messages for a guild', async () => {
|
||||||
|
await guildMember.sync();
|
||||||
|
const initialNotifications = guildMember.notifications.length;
|
||||||
await guildMember.post(`/groups/${guild._id}/chat/seen`);
|
await guildMember.post(`/groups/${guild._id}/chat/seen`);
|
||||||
|
|
||||||
let guildThatHasSeenChat = await guildMember.get('/user');
|
let guildThatHasSeenChat = await guildMember.get('/user');
|
||||||
|
|
||||||
|
expect(guildThatHasSeenChat.notifications.length).to.equal(initialNotifications - 1);
|
||||||
expect(guildThatHasSeenChat.newMessages).to.be.empty;
|
expect(guildThatHasSeenChat.newMessages).to.be.empty;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -53,10 +56,13 @@ describe('POST /groups/:id/chat/seen', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('clears new messages for a party', async () => {
|
it('clears new messages for a party', async () => {
|
||||||
|
await partyMember.sync();
|
||||||
|
const initialNotifications = partyMember.notifications.length;
|
||||||
await partyMember.post(`/groups/${party._id}/chat/seen`);
|
await partyMember.post(`/groups/${party._id}/chat/seen`);
|
||||||
|
|
||||||
let partyMemberThatHasSeenChat = await partyMember.get('/user');
|
let partyMemberThatHasSeenChat = await partyMember.get('/user');
|
||||||
|
|
||||||
|
expect(partyMemberThatHasSeenChat.notifications.length).to.equal(initialNotifications - 1);
|
||||||
expect(partyMemberThatHasSeenChat.newMessages).to.be.empty;
|
expect(partyMemberThatHasSeenChat.newMessages).to.be.empty;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import {
|
|||||||
generateUser,
|
generateUser,
|
||||||
translate as t,
|
translate as t,
|
||||||
} from '../../../../helpers/api-v3-integration.helper';
|
} from '../../../../helpers/api-v3-integration.helper';
|
||||||
|
import config from '../../../../../config.json';
|
||||||
import { v4 as generateUUID } from 'uuid';
|
import { v4 as generateUUID } from 'uuid';
|
||||||
|
|
||||||
describe('POST /groups/:id/chat/:id/clearflags', () => {
|
describe('POST /groups/:id/chat/:id/clearflags', () => {
|
||||||
@@ -74,7 +75,7 @@ describe('POST /groups/:id/chat/:id/clearflags', () => {
|
|||||||
expect(messages[0].flagCount).to.eql(0);
|
expect(messages[0].flagCount).to.eql(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can unflag a system message', async () => {
|
it('can\'t flag a system message', async () => {
|
||||||
let { group, members } = await createAndPopulateGroup({
|
let { group, members } = await createAndPopulateGroup({
|
||||||
groupDetails: {
|
groupDetails: {
|
||||||
type: 'party',
|
type: 'party',
|
||||||
@@ -95,13 +96,15 @@ describe('POST /groups/:id/chat/:id/clearflags', () => {
|
|||||||
await member.post('/user/class/cast/mpheal');
|
await member.post('/user/class/cast/mpheal');
|
||||||
|
|
||||||
let [skillMsg] = await member.get(`/groups/${group.id}/chat`);
|
let [skillMsg] = await member.get(`/groups/${group.id}/chat`);
|
||||||
|
await expect(member.post(`/groups/${group._id}/chat/${skillMsg.id}/flag`))
|
||||||
await member.post(`/groups/${group._id}/chat/${skillMsg.id}/flag`);
|
.to.eventually.be.rejected.and.eql({
|
||||||
await admin.post(`/groups/${group._id}/chat/${skillMsg.id}/clearflags`);
|
code: 400,
|
||||||
|
error: 'BadRequest',
|
||||||
let messages = await members[0].get(`/groups/${group._id}/chat`);
|
message: t('messageCannotFlagSystemMessages', {communityManagerEmail: config.EMAILS.COMMUNITY_MANAGER_EMAIL}),
|
||||||
expect(messages[0].id).to.eql(skillMsg.id);
|
});
|
||||||
expect(messages[0].flagCount).to.eql(0);
|
// let messages = await members[0].get(`/groups/${group._id}/chat`);
|
||||||
|
// expect(messages[0].id).to.eql(skillMsg.id);
|
||||||
|
// expect(messages[0].flagCount).to.eql(0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import {
|
import {
|
||||||
generateUser,
|
generateUser,
|
||||||
translate as t,
|
|
||||||
resetHabiticaDB,
|
resetHabiticaDB,
|
||||||
} from '../../../../helpers/api-v3-integration.helper';
|
} from '../../../../helpers/api-v3-integration.helper';
|
||||||
|
import apiMessages from '../../../../../website/server/libs/apiMessages';
|
||||||
|
|
||||||
describe('GET /coupons/', () => {
|
describe('GET /coupons/', () => {
|
||||||
let user;
|
let user;
|
||||||
@@ -19,7 +19,7 @@ describe('GET /coupons/', () => {
|
|||||||
await expect(user.get('/coupons')).to.eventually.be.rejected.and.eql({
|
await expect(user.get('/coupons')).to.eventually.be.rejected.and.eql({
|
||||||
code: 401,
|
code: 401,
|
||||||
error: 'NotAuthorized',
|
error: 'NotAuthorized',
|
||||||
message: t('noSudoAccess'),
|
message: apiMessages('noSudoAccess'),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import {
|
|||||||
resetHabiticaDB,
|
resetHabiticaDB,
|
||||||
} from '../../../../helpers/api-v3-integration.helper';
|
} from '../../../../helpers/api-v3-integration.helper';
|
||||||
import couponCode from 'coupon-code';
|
import couponCode from 'coupon-code';
|
||||||
|
import apiMessages from '../../../../../website/server/libs/apiMessages';
|
||||||
|
|
||||||
describe('POST /coupons/generate/:event', () => {
|
describe('POST /coupons/generate/:event', () => {
|
||||||
let user;
|
let user;
|
||||||
@@ -25,7 +26,7 @@ describe('POST /coupons/generate/:event', () => {
|
|||||||
await expect(user.post('/coupons/generate/aaa')).to.eventually.be.rejected.and.eql({
|
await expect(user.post('/coupons/generate/aaa')).to.eventually.be.rejected.and.eql({
|
||||||
code: 401,
|
code: 401,
|
||||||
error: 'NotAuthorized',
|
error: 'NotAuthorized',
|
||||||
message: t('noSudoAccess'),
|
message: apiMessages('noSudoAccess'),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import {
|
|||||||
} from '../../../../helpers/api-v3-integration.helper';
|
} from '../../../../helpers/api-v3-integration.helper';
|
||||||
import { v4 as generateUUID } from 'uuid';
|
import { v4 as generateUUID } from 'uuid';
|
||||||
|
|
||||||
describe('GET /export/avatar-:memberId.html', () => {
|
xdescribe('GET /export/avatar-:memberId.html', () => {
|
||||||
let user;
|
let user;
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
|
|||||||
32
test/api/v3/integration/groups/GET-group-plans.test.js
Normal file
32
test/api/v3/integration/groups/GET-group-plans.test.js
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import {
|
||||||
|
generateUser,
|
||||||
|
generateGroup,
|
||||||
|
} from '../../../../helpers/api-v3-integration.helper';
|
||||||
|
|
||||||
|
describe('GET /group-plans', () => {
|
||||||
|
let user;
|
||||||
|
let groupPlan;
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
user = await generateUser({balance: 4});
|
||||||
|
groupPlan = await generateGroup(user,
|
||||||
|
{
|
||||||
|
name: 'public guild - is member',
|
||||||
|
type: 'guild',
|
||||||
|
privacy: 'public',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
purchased: {
|
||||||
|
plan: {
|
||||||
|
customerId: 'existings',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns group plans for the user', async () => {
|
||||||
|
let groupPlans = await user.get('/group-plans');
|
||||||
|
|
||||||
|
expect(groupPlans[0]._id).to.eql(groupPlan._id);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -72,7 +72,7 @@ describe('GET /groups/:groupId/members', () => {
|
|||||||
|
|
||||||
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
|
||||||
'_id', 'id', 'preferences', 'profile', 'stats', 'achievements', 'party',
|
'_id', 'id', 'preferences', 'profile', 'stats', 'achievements', 'party',
|
||||||
'backer', 'contributor', 'auth', 'items', 'inbox',
|
'backer', 'contributor', 'auth', 'items', 'inbox', 'loginIncentives',
|
||||||
]);
|
]);
|
||||||
expect(Object.keys(memberRes.auth)).to.eql(['timestamps']);
|
expect(Object.keys(memberRes.auth)).to.eql(['timestamps']);
|
||||||
expect(Object.keys(memberRes.preferences).sort()).to.eql([
|
expect(Object.keys(memberRes.preferences).sort()).to.eql([
|
||||||
@@ -93,7 +93,7 @@ describe('GET /groups/:groupId/members', () => {
|
|||||||
|
|
||||||
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
|
||||||
'_id', 'id', 'preferences', 'profile', 'stats', 'achievements', 'party',
|
'_id', 'id', 'preferences', 'profile', 'stats', 'achievements', 'party',
|
||||||
'backer', 'contributor', 'auth', 'items', 'inbox',
|
'backer', 'contributor', 'auth', 'items', 'inbox', 'loginIncentives',
|
||||||
]);
|
]);
|
||||||
expect(Object.keys(memberRes.auth)).to.eql(['timestamps']);
|
expect(Object.keys(memberRes.auth)).to.eql(['timestamps']);
|
||||||
expect(Object.keys(memberRes.preferences).sort()).to.eql([
|
expect(Object.keys(memberRes.preferences).sort()).to.eql([
|
||||||
@@ -161,4 +161,19 @@ describe('GET /groups/:groupId/members', () => {
|
|||||||
let resIds = res.concat(res2).map(member => member._id);
|
let resIds = res.concat(res2).map(member => member._id);
|
||||||
expect(resIds).to.eql(expectedIds.sort());
|
expect(resIds).to.eql(expectedIds.sort());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('searches members', async () => {
|
||||||
|
let group = await generateGroup(user, {type: 'party', name: generateUUID()});
|
||||||
|
|
||||||
|
let usersToGenerate = [];
|
||||||
|
for (let i = 0; i < 2; i++) {
|
||||||
|
usersToGenerate.push(generateUser({party: {_id: group._id}}));
|
||||||
|
}
|
||||||
|
const usersCreated = await Promise.all(usersToGenerate);
|
||||||
|
const userToSearch = usersCreated[0].profile.name;
|
||||||
|
|
||||||
|
let res = await user.get(`/groups/party/members?search=${userToSearch}`);
|
||||||
|
expect(res.length).to.equal(1);
|
||||||
|
expect(res[0].profile.name).to.equal(userToSearch);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import { v4 as generateUUID } from 'uuid';
|
|||||||
import {
|
import {
|
||||||
each,
|
each,
|
||||||
} from 'lodash';
|
} from 'lodash';
|
||||||
|
import { model as User } from '../../../../../website/server/models/user';
|
||||||
|
import * as payments from '../../../../../website/server/libs/payments';
|
||||||
|
|
||||||
describe('POST /groups/:groupId/leave', () => {
|
describe('POST /groups/:groupId/leave', () => {
|
||||||
let typesOfGroups = {
|
let typesOfGroups = {
|
||||||
@@ -68,13 +70,21 @@ 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 member.post(`/groups/${groupToLeave._id}/chat`, { message: 'Some message' });
|
||||||
|
|
||||||
|
await sleep(0.5);
|
||||||
|
|
||||||
await leader.sync();
|
await leader.sync();
|
||||||
|
|
||||||
|
expect(leader.notifications.find(n => {
|
||||||
|
return n.type === 'NEW_CHAT_MESSAGE' && n.data.group.id === groupToLeave._id;
|
||||||
|
})).to.exist;
|
||||||
expect(leader.newMessages[groupToLeave._id]).to.not.be.empty;
|
expect(leader.newMessages[groupToLeave._id]).to.not.be.empty;
|
||||||
|
|
||||||
await leader.post(`/groups/${groupToLeave._id}/leave`);
|
await leader.post(`/groups/${groupToLeave._id}/leave`);
|
||||||
await leader.sync();
|
await leader.sync();
|
||||||
|
|
||||||
|
expect(leader.notifications.find(n => {
|
||||||
|
return n.type === 'NEW_CHAT_MESSAGE' && n.data.group.id === groupToLeave._id;
|
||||||
|
})).to.not.exist;
|
||||||
expect(leader.newMessages[groupToLeave._id]).to.be.empty;
|
expect(leader.newMessages[groupToLeave._id]).to.be.empty;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -264,4 +274,45 @@ describe('POST /groups/:groupId/leave', () => {
|
|||||||
expect(userWithNonExistentParty.party).to.eql({});
|
expect(userWithNonExistentParty.party).to.eql({});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
context('Leaving a group plan', () => {
|
||||||
|
it('cancels the free subscription', async () => {
|
||||||
|
// Create group
|
||||||
|
let { group, groupLeader, members } = await createAndPopulateGroup({
|
||||||
|
groupDetails: {
|
||||||
|
name: 'Test Private Guild',
|
||||||
|
type: 'guild',
|
||||||
|
},
|
||||||
|
members: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
let leader = groupLeader;
|
||||||
|
let member = members[0];
|
||||||
|
let userWithFreePlan = await User.findById(leader._id).exec();
|
||||||
|
|
||||||
|
// Create subscription
|
||||||
|
let paymentData = {
|
||||||
|
user: userWithFreePlan,
|
||||||
|
groupId: group._id,
|
||||||
|
sub: {
|
||||||
|
key: 'basic_3mo',
|
||||||
|
},
|
||||||
|
customerId: 'customer-id',
|
||||||
|
paymentMethod: 'Payment Method',
|
||||||
|
headers: {
|
||||||
|
'x-client': 'habitica-web',
|
||||||
|
'user-agent': '',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
await payments.createSubscription(paymentData);
|
||||||
|
await member.sync();
|
||||||
|
expect(member.purchased.plan.planId).to.equal('group_plan_auto');
|
||||||
|
expect(member.purchased.plan.dateTerminated).to.not.exist;
|
||||||
|
|
||||||
|
// Leave
|
||||||
|
await member.post(`/groups/${group._id}/leave`);
|
||||||
|
await member.sync();
|
||||||
|
expect(member.purchased.plan.dateTerminated).to.exist;
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import {
|
|||||||
generateUser,
|
generateUser,
|
||||||
createAndPopulateGroup,
|
createAndPopulateGroup,
|
||||||
translate as t,
|
translate as t,
|
||||||
|
sleep,
|
||||||
} from '../../../../helpers/api-v3-integration.helper';
|
} from '../../../../helpers/api-v3-integration.helper';
|
||||||
import * as email from '../../../../../website/server/libs/email';
|
import * as email from '../../../../../website/server/libs/email';
|
||||||
|
|
||||||
@@ -188,13 +189,20 @@ describe('POST /groups/:groupId/removeMember/:memberId', () => {
|
|||||||
|
|
||||||
it('removes new messages from a member who is removed', async () => {
|
it('removes new messages from a member who is removed', async () => {
|
||||||
await partyLeader.post(`/groups/${party._id}/chat`, { message: 'Some message' });
|
await partyLeader.post(`/groups/${party._id}/chat`, { message: 'Some message' });
|
||||||
|
await sleep(0.5);
|
||||||
await removedMember.sync();
|
await removedMember.sync();
|
||||||
|
|
||||||
|
expect(removedMember.notifications.find(n => {
|
||||||
|
return n.type === 'NEW_CHAT_MESSAGE' && n.data.group.id === party._id;
|
||||||
|
})).to.exist;
|
||||||
expect(removedMember.newMessages[party._id]).to.not.be.empty;
|
expect(removedMember.newMessages[party._id]).to.not.be.empty;
|
||||||
|
|
||||||
await partyLeader.post(`/groups/${party._id}/removeMember/${removedMember._id}`);
|
await partyLeader.post(`/groups/${party._id}/removeMember/${removedMember._id}`);
|
||||||
await removedMember.sync();
|
await removedMember.sync();
|
||||||
|
|
||||||
|
expect(removedMember.notifications.find(n => {
|
||||||
|
return n.type === 'NEW_CHAT_MESSAGE' && n.data.group.id === party._id;
|
||||||
|
})).to.not.exist;
|
||||||
expect(removedMember.newMessages[party._id]).to.be.empty;
|
expect(removedMember.newMessages[party._id]).to.be.empty;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -110,6 +110,7 @@ describe('Post /groups/:groupId/invite', () => {
|
|||||||
id: group._id,
|
id: group._id,
|
||||||
name: groupName,
|
name: groupName,
|
||||||
inviter: inviter._id,
|
inviter: inviter._id,
|
||||||
|
publicGuild: false,
|
||||||
}]);
|
}]);
|
||||||
|
|
||||||
await expect(userToInvite.get('/user'))
|
await expect(userToInvite.get('/user'))
|
||||||
@@ -127,11 +128,13 @@ describe('Post /groups/:groupId/invite', () => {
|
|||||||
id: group._id,
|
id: group._id,
|
||||||
name: groupName,
|
name: groupName,
|
||||||
inviter: inviter._id,
|
inviter: inviter._id,
|
||||||
|
publicGuild: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: group._id,
|
id: group._id,
|
||||||
name: groupName,
|
name: groupName,
|
||||||
inviter: inviter._id,
|
inviter: inviter._id,
|
||||||
|
publicGuild: false,
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ describe('GET /members/:memberId', () => {
|
|||||||
let memberRes = await user.get(`/members/${member._id}`);
|
let memberRes = await user.get(`/members/${member._id}`);
|
||||||
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
|
||||||
'_id', 'id', 'preferences', 'profile', 'stats', 'achievements', 'party',
|
'_id', 'id', 'preferences', 'profile', 'stats', 'achievements', 'party',
|
||||||
'backer', 'contributor', 'auth', 'items', 'inbox',
|
'backer', 'contributor', 'auth', 'items', 'inbox', 'loginIncentives',
|
||||||
]);
|
]);
|
||||||
expect(Object.keys(memberRes.auth)).to.eql(['timestamps']);
|
expect(Object.keys(memberRes.auth)).to.eql(['timestamps']);
|
||||||
expect(Object.keys(memberRes.preferences).sort()).to.eql([
|
expect(Object.keys(memberRes.preferences).sort()).to.eql([
|
||||||
|
|||||||
@@ -98,6 +98,7 @@ describe('POST /members/send-private-message', () => {
|
|||||||
|
|
||||||
it('sends a private message to a user', async () => {
|
it('sends a private message to a user', async () => {
|
||||||
let receiver = await generateUser();
|
let receiver = await generateUser();
|
||||||
|
// const initialNotifications = receiver.notifications.length;
|
||||||
|
|
||||||
await userToSendMessage.post('/members/send-private-message', {
|
await userToSendMessage.post('/members/send-private-message', {
|
||||||
message: messageToSend,
|
message: messageToSend,
|
||||||
@@ -115,6 +116,92 @@ describe('POST /members/send-private-message', () => {
|
|||||||
return message.uuid === receiver._id && message.text === messageToSend;
|
return message.uuid === receiver._id && message.text === messageToSend;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// @TODO waiting for mobile support
|
||||||
|
// expect(updatedReceiver.notifications.length).to.equal(initialNotifications + 1);
|
||||||
|
// const notification = updatedReceiver.notifications[updatedReceiver.notifications.length - 1];
|
||||||
|
|
||||||
|
// expect(notification.type).to.equal('NEW_INBOX_MESSAGE');
|
||||||
|
// expect(notification.data.messageId).to.equal(sendersMessageInReceiversInbox.id);
|
||||||
|
// expect(notification.data.excerpt).to.equal(messageToSend);
|
||||||
|
// expect(notification.data.sender.id).to.equal(updatedSender._id);
|
||||||
|
// expect(notification.data.sender.name).to.equal(updatedSender.profile.name);
|
||||||
|
|
||||||
|
expect(sendersMessageInReceiversInbox).to.exist;
|
||||||
|
expect(sendersMessageInSendersInbox).to.exist;
|
||||||
|
});
|
||||||
|
|
||||||
|
// @TODO waiting for mobile support
|
||||||
|
xit('creates a notification with an excerpt if the message is too long', async () => {
|
||||||
|
let receiver = await generateUser();
|
||||||
|
let longerMessageToSend = 'A very long message, that for sure exceeds the limit of 100 chars for the excerpt that we set to 100 chars';
|
||||||
|
let messageExcerpt = `${longerMessageToSend.substring(0, 100)}...`;
|
||||||
|
|
||||||
|
await userToSendMessage.post('/members/send-private-message', {
|
||||||
|
message: longerMessageToSend,
|
||||||
|
toUserId: receiver._id,
|
||||||
|
});
|
||||||
|
|
||||||
|
let updatedReceiver = await receiver.get('/user');
|
||||||
|
|
||||||
|
let sendersMessageInReceiversInbox = _.find(updatedReceiver.inbox.messages, (message) => {
|
||||||
|
return message.uuid === userToSendMessage._id && message.text === longerMessageToSend;
|
||||||
|
});
|
||||||
|
|
||||||
|
const notification = updatedReceiver.notifications[updatedReceiver.notifications.length - 1];
|
||||||
|
|
||||||
|
expect(notification.type).to.equal('NEW_INBOX_MESSAGE');
|
||||||
|
expect(notification.data.messageId).to.equal(sendersMessageInReceiversInbox.id);
|
||||||
|
expect(notification.data.excerpt).to.equal(messageExcerpt);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('allows admin to send when sender has blocked the admin', async () => {
|
||||||
|
userToSendMessage = await generateUser({
|
||||||
|
'contributor.admin': 1,
|
||||||
|
});
|
||||||
|
const receiver = await generateUser({'inbox.blocks': [userToSendMessage._id]});
|
||||||
|
|
||||||
|
await userToSendMessage.post('/members/send-private-message', {
|
||||||
|
message: messageToSend,
|
||||||
|
toUserId: receiver._id,
|
||||||
|
});
|
||||||
|
|
||||||
|
const updatedReceiver = await receiver.get('/user');
|
||||||
|
const updatedSender = await userToSendMessage.get('/user');
|
||||||
|
|
||||||
|
const sendersMessageInReceiversInbox = _.find(updatedReceiver.inbox.messages, (message) => {
|
||||||
|
return message.uuid === userToSendMessage._id && message.text === messageToSend;
|
||||||
|
});
|
||||||
|
|
||||||
|
const sendersMessageInSendersInbox = _.find(updatedSender.inbox.messages, (message) => {
|
||||||
|
return message.uuid === receiver._id && message.text === messageToSend;
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(sendersMessageInReceiversInbox).to.exist;
|
||||||
|
expect(sendersMessageInSendersInbox).to.exist;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('allows admin to send when to user has opted out of messaging', async () => {
|
||||||
|
userToSendMessage = await generateUser({
|
||||||
|
'contributor.admin': 1,
|
||||||
|
});
|
||||||
|
const receiver = await generateUser({'inbox.optOut': true});
|
||||||
|
|
||||||
|
await userToSendMessage.post('/members/send-private-message', {
|
||||||
|
message: messageToSend,
|
||||||
|
toUserId: receiver._id,
|
||||||
|
});
|
||||||
|
|
||||||
|
const updatedReceiver = await receiver.get('/user');
|
||||||
|
const updatedSender = await userToSendMessage.get('/user');
|
||||||
|
|
||||||
|
const sendersMessageInReceiversInbox = _.find(updatedReceiver.inbox.messages, (message) => {
|
||||||
|
return message.uuid === userToSendMessage._id && message.text === messageToSend;
|
||||||
|
});
|
||||||
|
|
||||||
|
const sendersMessageInSendersInbox = _.find(updatedSender.inbox.messages, (message) => {
|
||||||
|
return message.uuid === receiver._id && message.text === messageToSend;
|
||||||
|
});
|
||||||
|
|
||||||
expect(sendersMessageInReceiversInbox).to.exist;
|
expect(sendersMessageInReceiversInbox).to.exist;
|
||||||
expect(sendersMessageInSendersInbox).to.exist;
|
expect(sendersMessageInSendersInbox).to.exist;
|
||||||
});
|
});
|
||||||
|
|||||||
16
test/api/v3/integration/news/GET-news.test.js
Normal file
16
test/api/v3/integration/news/GET-news.test.js
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import {
|
||||||
|
requester,
|
||||||
|
} from '../../../../helpers/api-v3-integration.helper';
|
||||||
|
|
||||||
|
describe('GET /news', () => {
|
||||||
|
let api;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
api = requester();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns the latest news in html format, does not require authentication', async () => {
|
||||||
|
const res = await api.get('/news');
|
||||||
|
expect(res).to.be.a.string;
|
||||||
|
});
|
||||||
|
});
|
||||||
42
test/api/v3/integration/news/POST-news_tell_me_later.test.js
Normal file
42
test/api/v3/integration/news/POST-news_tell_me_later.test.js
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import {
|
||||||
|
generateUser,
|
||||||
|
} from '../../../../helpers/api-v3-integration.helper';
|
||||||
|
|
||||||
|
describe('POST /news/tell-me-later', () => {
|
||||||
|
let user;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
user = await generateUser({
|
||||||
|
'flags.newStuff': true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('marks new stuff as read and adds notification', async () => {
|
||||||
|
expect(user.flags.newStuff).to.equal(true);
|
||||||
|
const initialNotifications = user.notifications.length;
|
||||||
|
|
||||||
|
await user.post('/news/tell-me-later');
|
||||||
|
await user.sync();
|
||||||
|
|
||||||
|
expect(user.flags.newStuff).to.equal(false);
|
||||||
|
expect(user.notifications.length).to.equal(initialNotifications + 1);
|
||||||
|
|
||||||
|
const notification = user.notifications[user.notifications.length - 1];
|
||||||
|
|
||||||
|
expect(notification.type).to.equal('NEW_STUFF');
|
||||||
|
// should be marked as seen by default so it's not counted in the number of notifications
|
||||||
|
expect(notification.seen).to.equal(true);
|
||||||
|
expect(notification.data.title).to.be.a.string;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('never adds two notifications', async () => {
|
||||||
|
const initialNotifications = user.notifications.length;
|
||||||
|
|
||||||
|
await user.post('/news/tell-me-later');
|
||||||
|
await user.post('/news/tell-me-later');
|
||||||
|
|
||||||
|
await user.sync();
|
||||||
|
|
||||||
|
expect(user.notifications.length).to.equal(initialNotifications + 1);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -13,14 +13,45 @@ describe('POST /notifications/:notificationId/read', () => {
|
|||||||
|
|
||||||
it('errors when notification is not found', async () => {
|
it('errors when notification is not found', async () => {
|
||||||
let dummyId = generateUUID();
|
let dummyId = generateUUID();
|
||||||
await expect(user.post(`/notifications/${dummyId}/read`))
|
|
||||||
.to.eventually.be.rejected.and.eql({
|
await expect(user.post(`/notifications/${dummyId}/read`)).to.eventually.be.rejected.and.eql({
|
||||||
code: 404,
|
code: 404,
|
||||||
error: 'NotFound',
|
error: 'NotFound',
|
||||||
message: t('messageNotificationNotFound'),
|
message: t('messageNotificationNotFound'),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
xit('removes a notification', async () => {
|
it('removes a notification', async () => {
|
||||||
|
expect(user.notifications.length).to.equal(0);
|
||||||
|
|
||||||
|
const id = generateUUID();
|
||||||
|
const id2 = generateUUID();
|
||||||
|
|
||||||
|
await user.update({
|
||||||
|
notifications: [{
|
||||||
|
id,
|
||||||
|
type: 'DROPS_ENABLED',
|
||||||
|
data: {},
|
||||||
|
}, {
|
||||||
|
id: id2,
|
||||||
|
type: 'LOGIN_INCENTIVE',
|
||||||
|
data: {},
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
|
||||||
|
await user.sync();
|
||||||
|
expect(user.notifications.length).to.equal(2);
|
||||||
|
|
||||||
|
const res = await user.post(`/notifications/${id}/read`);
|
||||||
|
expect(res).to.deep.equal([{
|
||||||
|
id: id2,
|
||||||
|
type: 'LOGIN_INCENTIVE',
|
||||||
|
data: {},
|
||||||
|
seen: false,
|
||||||
|
}]);
|
||||||
|
|
||||||
|
await user.sync();
|
||||||
|
expect(user.notifications.length).to.equal(1);
|
||||||
|
expect(user.notifications[0].id).to.equal(id2);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
import {
|
||||||
|
generateUser,
|
||||||
|
translate as t,
|
||||||
|
} from '../../../../helpers/api-v3-integration.helper';
|
||||||
|
import { v4 as generateUUID } from 'uuid';
|
||||||
|
|
||||||
|
describe('POST /notifications/:notificationId/see', () => {
|
||||||
|
let user;
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
user = await generateUser();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('errors when notification is not found', async () => {
|
||||||
|
let dummyId = generateUUID();
|
||||||
|
|
||||||
|
await expect(user.post(`/notifications/${dummyId}/see`)).to.eventually.be.rejected.and.eql({
|
||||||
|
code: 404,
|
||||||
|
error: 'NotFound',
|
||||||
|
message: t('messageNotificationNotFound'),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('mark a notification as seen', async () => {
|
||||||
|
expect(user.notifications.length).to.equal(0);
|
||||||
|
|
||||||
|
const id = generateUUID();
|
||||||
|
const id2 = generateUUID();
|
||||||
|
|
||||||
|
await user.update({
|
||||||
|
notifications: [{
|
||||||
|
id,
|
||||||
|
type: 'DROPS_ENABLED',
|
||||||
|
data: {},
|
||||||
|
}, {
|
||||||
|
id: id2,
|
||||||
|
type: 'LOGIN_INCENTIVE',
|
||||||
|
data: {},
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
|
||||||
|
const userObj = await user.get('/user'); // so we can check that defaults have been applied
|
||||||
|
expect(userObj.notifications.length).to.equal(2);
|
||||||
|
expect(userObj.notifications[0].seen).to.equal(false);
|
||||||
|
|
||||||
|
const res = await user.post(`/notifications/${id}/see`);
|
||||||
|
expect(res).to.deep.equal({
|
||||||
|
id,
|
||||||
|
type: 'DROPS_ENABLED',
|
||||||
|
data: {},
|
||||||
|
seen: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
await user.sync();
|
||||||
|
expect(user.notifications.length).to.equal(2);
|
||||||
|
expect(user.notifications[0].id).to.equal(id);
|
||||||
|
expect(user.notifications[0].seen).to.equal(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
import {
|
||||||
|
generateUser,
|
||||||
|
translate as t,
|
||||||
|
} from '../../../../helpers/api-v3-integration.helper';
|
||||||
|
import { v4 as generateUUID } from 'uuid';
|
||||||
|
|
||||||
|
describe('POST /notifications/read', () => {
|
||||||
|
let user;
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
user = await generateUser();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('errors when notification is not found', async () => {
|
||||||
|
let dummyId = generateUUID();
|
||||||
|
|
||||||
|
await expect(user.post('/notifications/read', {
|
||||||
|
notificationIds: [dummyId],
|
||||||
|
})).to.eventually.be.rejected.and.eql({
|
||||||
|
code: 404,
|
||||||
|
error: 'NotFound',
|
||||||
|
message: t('messageNotificationNotFound'),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('removes multiple notifications', async () => {
|
||||||
|
expect(user.notifications.length).to.equal(0);
|
||||||
|
|
||||||
|
const id = generateUUID();
|
||||||
|
const id2 = generateUUID();
|
||||||
|
const id3 = generateUUID();
|
||||||
|
|
||||||
|
await user.update({
|
||||||
|
notifications: [{
|
||||||
|
id,
|
||||||
|
type: 'DROPS_ENABLED',
|
||||||
|
data: {},
|
||||||
|
}, {
|
||||||
|
id: id2,
|
||||||
|
type: 'LOGIN_INCENTIVE',
|
||||||
|
data: {},
|
||||||
|
}, {
|
||||||
|
id: id3,
|
||||||
|
type: 'CRON',
|
||||||
|
data: {},
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
|
||||||
|
await user.sync();
|
||||||
|
expect(user.notifications.length).to.equal(3);
|
||||||
|
|
||||||
|
const res = await user.post('/notifications/read', {
|
||||||
|
notificationIds: [id, id3],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res).to.deep.equal([{
|
||||||
|
id: id2,
|
||||||
|
type: 'LOGIN_INCENTIVE',
|
||||||
|
data: {},
|
||||||
|
seen: false,
|
||||||
|
}]);
|
||||||
|
|
||||||
|
await user.sync();
|
||||||
|
expect(user.notifications.length).to.equal(1);
|
||||||
|
expect(user.notifications[0].id).to.equal(id2);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
import {
|
||||||
|
generateUser,
|
||||||
|
translate as t,
|
||||||
|
} from '../../../../helpers/api-v3-integration.helper';
|
||||||
|
import { v4 as generateUUID } from 'uuid';
|
||||||
|
|
||||||
|
describe('POST /notifications/see', () => {
|
||||||
|
let user;
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
user = await generateUser();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('errors when notification is not found', async () => {
|
||||||
|
let dummyId = generateUUID();
|
||||||
|
|
||||||
|
await expect(user.post('/notifications/see', {
|
||||||
|
notificationIds: [dummyId],
|
||||||
|
})).to.eventually.be.rejected.and.eql({
|
||||||
|
code: 404,
|
||||||
|
error: 'NotFound',
|
||||||
|
message: t('messageNotificationNotFound'),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('mark multiple notifications as seen', async () => {
|
||||||
|
expect(user.notifications.length).to.equal(0);
|
||||||
|
|
||||||
|
const id = generateUUID();
|
||||||
|
const id2 = generateUUID();
|
||||||
|
const id3 = generateUUID();
|
||||||
|
|
||||||
|
await user.update({
|
||||||
|
notifications: [{
|
||||||
|
id,
|
||||||
|
type: 'DROPS_ENABLED',
|
||||||
|
data: {},
|
||||||
|
seen: false,
|
||||||
|
}, {
|
||||||
|
id: id2,
|
||||||
|
type: 'LOGIN_INCENTIVE',
|
||||||
|
data: {},
|
||||||
|
seen: false,
|
||||||
|
}, {
|
||||||
|
id: id3,
|
||||||
|
type: 'CRON',
|
||||||
|
data: {},
|
||||||
|
seen: false,
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
|
||||||
|
await user.sync();
|
||||||
|
expect(user.notifications.length).to.equal(3);
|
||||||
|
|
||||||
|
const res = await user.post('/notifications/see', {
|
||||||
|
notificationIds: [id, id3],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res).to.deep.equal([
|
||||||
|
{
|
||||||
|
id,
|
||||||
|
type: 'DROPS_ENABLED',
|
||||||
|
data: {},
|
||||||
|
seen: true,
|
||||||
|
}, {
|
||||||
|
id: id2,
|
||||||
|
type: 'LOGIN_INCENTIVE',
|
||||||
|
data: {},
|
||||||
|
seen: false,
|
||||||
|
}, {
|
||||||
|
id: id3,
|
||||||
|
type: 'CRON',
|
||||||
|
data: {},
|
||||||
|
seen: true,
|
||||||
|
}]);
|
||||||
|
|
||||||
|
await user.sync();
|
||||||
|
expect(user.notifications.length).to.equal(3);
|
||||||
|
expect(user.notifications[0].id).to.equal(id);
|
||||||
|
expect(user.notifications[0].seen).to.equal(true);
|
||||||
|
|
||||||
|
expect(user.notifications[1].id).to.equal(id2);
|
||||||
|
expect(user.notifications[1].seen).to.equal(false);
|
||||||
|
|
||||||
|
expect(user.notifications[2].id).to.equal(id3);
|
||||||
|
expect(user.notifications[2].seen).to.equal(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -6,7 +6,7 @@ import superagent from 'superagent';
|
|||||||
import nconf from 'nconf';
|
import nconf from 'nconf';
|
||||||
|
|
||||||
const API_TEST_SERVER_PORT = nconf.get('PORT');
|
const API_TEST_SERVER_PORT = nconf.get('PORT');
|
||||||
describe('GET /qr-code/user/:memberId', () => {
|
xdescribe('GET /qr-code/user/:memberId', () => {
|
||||||
let user;
|
let user;
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
|
|||||||
@@ -141,6 +141,16 @@ describe('DELETE /tasks/:id', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('removes a task from user.tasksOrder'); // TODO
|
it('removes a task from user.tasksOrder', async () => {
|
||||||
|
let task = await user.post('/tasks/user', {
|
||||||
|
text: 'test habit',
|
||||||
|
type: 'habit',
|
||||||
|
});
|
||||||
|
|
||||||
|
await user.del(`/tasks/${task._id}`);
|
||||||
|
await user.sync();
|
||||||
|
|
||||||
|
expect(user.tasksOrder.habits.indexOf(task._id)).to.eql(-1);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ describe('GET /tasks/user', () => {
|
|||||||
it('returns all user\'s tasks', async () => {
|
it('returns all user\'s tasks', async () => {
|
||||||
let createdTasks = await user.post('/tasks/user', [{text: 'test habit', type: 'habit'}, {text: 'test todo', type: 'todo'}]);
|
let createdTasks = await user.post('/tasks/user', [{text: 'test habit', type: 'habit'}, {text: 'test todo', type: 'todo'}]);
|
||||||
let tasks = await user.get('/tasks/user');
|
let tasks = await user.get('/tasks/user');
|
||||||
expect(tasks.length).to.equal(createdTasks.length + 1); // + 1 because 1 is a default task
|
expect(tasks.length).to.equal(createdTasks.length + 1); // Plus one for generated todo
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns only a type of user\'s tasks if req.query.type is specified', async () => {
|
it('returns only a type of user\'s tasks if req.query.type is specified', async () => {
|
||||||
|
|||||||
@@ -130,6 +130,7 @@ describe('POST /tasks/:id/score/:direction', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('uncompletes todo when direction is down', async () => {
|
it('uncompletes todo when direction is down', async () => {
|
||||||
|
await user.post(`/tasks/${todo._id}/score/up`);
|
||||||
await user.post(`/tasks/${todo._id}/score/down`);
|
await user.post(`/tasks/${todo._id}/score/down`);
|
||||||
let updatedTask = await user.get(`/tasks/${todo._id}`);
|
let updatedTask = await user.get(`/tasks/${todo._id}`);
|
||||||
|
|
||||||
@@ -137,9 +138,23 @@ describe('POST /tasks/:id/score/:direction', () => {
|
|||||||
expect(updatedTask.dateCompleted).to.be.a('undefined');
|
expect(updatedTask.dateCompleted).to.be.a('undefined');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('scores up todo even if it is already completed'); // Yes?
|
it('doesn\'t let a todo be completed twice', async () => {
|
||||||
|
await user.post(`/tasks/${todo._id}/score/up`);
|
||||||
|
await expect(user.post(`/tasks/${todo._id}/score/up`))
|
||||||
|
.to.eventually.be.rejected.and.eql({
|
||||||
|
code: 401,
|
||||||
|
error: 'NotAuthorized',
|
||||||
|
message: t('sessionOutdated'),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('scores down todo even if it is already uncompleted'); // Yes?
|
it('doesn\'t let a todo be uncompleted twice', async () => {
|
||||||
|
await expect(user.post(`/tasks/${todo._id}/score/down`)).to.eventually.be.rejected.and.eql({
|
||||||
|
code: 401,
|
||||||
|
error: 'NotAuthorized',
|
||||||
|
message: t('sessionOutdated'),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
context('user stats when direction is up', () => {
|
context('user stats when direction is up', () => {
|
||||||
let updatedUser;
|
let updatedUser;
|
||||||
@@ -163,23 +178,25 @@ describe('POST /tasks/:id/score/:direction', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
context('user stats when direction is down', () => {
|
context('user stats when direction is down', () => {
|
||||||
let updatedUser;
|
let updatedUser, initialUser;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
|
await user.post(`/tasks/${todo._id}/score/up`);
|
||||||
|
initialUser = await user.get('/user');
|
||||||
await user.post(`/tasks/${todo._id}/score/down`);
|
await user.post(`/tasks/${todo._id}/score/down`);
|
||||||
updatedUser = await user.get('/user');
|
updatedUser = await user.get('/user');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('decreases user\'s mp', () => {
|
it('decreases user\'s mp', () => {
|
||||||
expect(updatedUser.stats.mp).to.be.lessThan(user.stats.mp);
|
expect(updatedUser.stats.mp).to.be.lessThan(initialUser.stats.mp);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('decreases user\'s exp', () => {
|
it('decreases user\'s exp', () => {
|
||||||
expect(updatedUser.stats.exp).to.be.lessThan(user.stats.exp);
|
expect(updatedUser.stats.exp).to.be.lessThan(initialUser.stats.exp);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('decreases user\'s gold', () => {
|
it('decreases user\'s gold', () => {
|
||||||
expect(updatedUser.stats.gp).to.be.lessThan(user.stats.gp);
|
expect(updatedUser.stats.gp).to.be.lessThan(initialUser.stats.gp);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -202,6 +219,7 @@ describe('POST /tasks/:id/score/:direction', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('uncompletes daily when direction is down', async () => {
|
it('uncompletes daily when direction is down', async () => {
|
||||||
|
await user.post(`/tasks/${daily._id}/score/up`);
|
||||||
await user.post(`/tasks/${daily._id}/score/down`);
|
await user.post(`/tasks/${daily._id}/score/down`);
|
||||||
let task = await user.get(`/tasks/${daily._id}`);
|
let task = await user.get(`/tasks/${daily._id}`);
|
||||||
|
|
||||||
@@ -222,9 +240,22 @@ describe('POST /tasks/:id/score/:direction', () => {
|
|||||||
expect(task.nextDue.length).to.eql(6);
|
expect(task.nextDue.length).to.eql(6);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('scores up daily even if it is already completed'); // Yes?
|
it('doesn\'t let a daily be completed twice', async () => {
|
||||||
|
await user.post(`/tasks/${daily._id}/score/up`);
|
||||||
|
await expect(user.post(`/tasks/${daily._id}/score/up`)).to.eventually.be.rejected.and.eql({
|
||||||
|
code: 401,
|
||||||
|
error: 'NotAuthorized',
|
||||||
|
message: t('sessionOutdated'),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('scores down daily even if it is already uncompleted'); // Yes?
|
it('doesn\'t let a daily be uncompleted twice', async () => {
|
||||||
|
await expect(user.post(`/tasks/${daily._id}/score/down`)).to.eventually.be.rejected.and.eql({
|
||||||
|
code: 401,
|
||||||
|
error: 'NotAuthorized',
|
||||||
|
message: t('sessionOutdated'),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
context('user stats when direction is up', () => {
|
context('user stats when direction is up', () => {
|
||||||
let updatedUser;
|
let updatedUser;
|
||||||
@@ -248,23 +279,25 @@ describe('POST /tasks/:id/score/:direction', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
context('user stats when direction is down', () => {
|
context('user stats when direction is down', () => {
|
||||||
let updatedUser;
|
let updatedUser, initialUser;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
|
await user.post(`/tasks/${daily._id}/score/up`);
|
||||||
|
initialUser = await user.get('/user');
|
||||||
await user.post(`/tasks/${daily._id}/score/down`);
|
await user.post(`/tasks/${daily._id}/score/down`);
|
||||||
updatedUser = await user.get('/user');
|
updatedUser = await user.get('/user');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('decreases user\'s mp', () => {
|
it('decreases user\'s mp', () => {
|
||||||
expect(updatedUser.stats.mp).to.be.lessThan(user.stats.mp);
|
expect(updatedUser.stats.mp).to.be.lessThan(initialUser.stats.mp);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('decreases user\'s exp', () => {
|
it('decreases user\'s exp', () => {
|
||||||
expect(updatedUser.stats.exp).to.be.lessThan(user.stats.exp);
|
expect(updatedUser.stats.exp).to.be.lessThan(initialUser.stats.exp);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('decreases user\'s gold', () => {
|
it('decreases user\'s gold', () => {
|
||||||
expect(updatedUser.stats.gp).to.be.lessThan(user.stats.gp);
|
expect(updatedUser.stats.gp).to.be.lessThan(initialUser.stats.gp);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -40,9 +40,13 @@ describe('POST /tasks/:taskId/move/to/:position', () => {
|
|||||||
|
|
||||||
let taskToMove = tasks[1];
|
let taskToMove = tasks[1];
|
||||||
expect(taskToMove.text).to.equal('habit 2');
|
expect(taskToMove.text).to.equal('habit 2');
|
||||||
|
|
||||||
let newOrder = await user.post(`/tasks/${tasks[1]._id}/move/to/3`);
|
let newOrder = await user.post(`/tasks/${tasks[1]._id}/move/to/3`);
|
||||||
|
await user.sync();
|
||||||
|
|
||||||
expect(newOrder[3]).to.equal(taskToMove._id);
|
expect(newOrder[3]).to.equal(taskToMove._id);
|
||||||
expect(newOrder.length).to.equal(5);
|
expect(newOrder.length).to.equal(5);
|
||||||
|
expect(user.tasksOrder.habits).to.eql(newOrder);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can move task to new position using alias', async () => {
|
it('can move task to new position using alias', async () => {
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ describe('POST /tasks/unlink-all/:challengeId', () => {
|
|||||||
// Have the leader delete the challenge and unlink the tasks
|
// Have the leader delete the challenge and unlink the tasks
|
||||||
await user.del(`/challenges/${challenge._id}`);
|
await user.del(`/challenges/${challenge._id}`);
|
||||||
await user.post(`/tasks/unlink-all/${challenge._id}?keep=keep-all`);
|
await user.post(`/tasks/unlink-all/${challenge._id}?keep=keep-all`);
|
||||||
// Get the second task for the second user
|
// Get the task for the second user
|
||||||
const [, anotherUserTask] = await anotherUser.get('/tasks/user');
|
const [, anotherUserTask] = await anotherUser.get('/tasks/user');
|
||||||
// Expect the second user to still have the task, but unlinked
|
// Expect the second user to still have the task, but unlinked
|
||||||
expect(anotherUserTask.challenge).to.eql({
|
expect(anotherUserTask.challenge).to.eql({
|
||||||
@@ -106,4 +106,4 @@ describe('POST /tasks/unlink-all/:challengeId', () => {
|
|||||||
winner: null,
|
winner: null,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ describe('POST /tasks/user', () => {
|
|||||||
expect(task.updatedAt).not.to.equal('tomorrow');
|
expect(task.updatedAt).not.to.equal('tomorrow');
|
||||||
expect(task.challenge).not.to.equal('no');
|
expect(task.challenge).not.to.equal('no');
|
||||||
expect(task.completed).to.equal(false);
|
expect(task.completed).to.equal(false);
|
||||||
expect(task.streak).not.to.equal('never');
|
expect(task.dateCompleted).not.to.equal('never');
|
||||||
expect(task.value).not.to.equal(324);
|
expect(task.value).not.to.equal(324);
|
||||||
expect(task.yesterDaily).to.equal(true);
|
expect(task.yesterDaily).to.equal(true);
|
||||||
});
|
});
|
||||||
@@ -302,6 +302,17 @@ describe('POST /tasks/user', () => {
|
|||||||
|
|
||||||
expect(task.alias).to.eql('a_alias012');
|
expect(task.alias).to.eql('a_alias012');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// This is a special case for iOS requests
|
||||||
|
it('will round a priority (difficulty)', async () => {
|
||||||
|
let task = await user.post('/tasks/user', {
|
||||||
|
text: 'test habit',
|
||||||
|
type: 'habit',
|
||||||
|
priority: 0.10000000000005,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(task.priority).to.eql(0.1);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
context('habits', () => {
|
context('habits', () => {
|
||||||
@@ -628,6 +639,43 @@ describe('POST /tasks/user', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('returns an error if everyX is a non int', async () => {
|
||||||
|
await expect(user.post('/tasks/user', {
|
||||||
|
text: 'test daily',
|
||||||
|
type: 'daily',
|
||||||
|
everyX: 2.5,
|
||||||
|
})).to.eventually.be.rejected.and.eql({
|
||||||
|
code: 400,
|
||||||
|
error: 'BadRequest',
|
||||||
|
message: 'daily validation failed',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns an error if everyX is negative', async () => {
|
||||||
|
await expect(user.post('/tasks/user', {
|
||||||
|
text: 'test daily',
|
||||||
|
type: 'daily',
|
||||||
|
everyX: -1,
|
||||||
|
})).to.eventually.be.rejected.and.eql({
|
||||||
|
code: 400,
|
||||||
|
error: 'BadRequest',
|
||||||
|
message: 'daily validation failed',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns an error if everyX is above 9999', async () => {
|
||||||
|
await expect(user.post('/tasks/user', {
|
||||||
|
text: 'test daily',
|
||||||
|
type: 'daily',
|
||||||
|
everyX: 10000,
|
||||||
|
})).to.eventually.be.rejected.and.eql({
|
||||||
|
code: 400,
|
||||||
|
error: 'BadRequest',
|
||||||
|
message: 'daily validation failed',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
it('can create checklists', async () => {
|
it('can create checklists', async () => {
|
||||||
let task = await user.post('/tasks/user', {
|
let task = await user.post('/tasks/user', {
|
||||||
text: 'test daily',
|
text: 'test daily',
|
||||||
|
|||||||
@@ -82,6 +82,13 @@ describe('POST /tasks/:id/score/:direction', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should update the history', async () => {
|
it('should update the history', async () => {
|
||||||
|
let newCron = new Date(2015, 11, 20);
|
||||||
|
|
||||||
|
await user.post('/debug/set-cron', {
|
||||||
|
lastCron: newCron,
|
||||||
|
});
|
||||||
|
|
||||||
|
await user.post('/cron');
|
||||||
await user.post(`/tasks/${usersChallengeTaskId}/score/up`);
|
await user.post(`/tasks/${usersChallengeTaskId}/score/up`);
|
||||||
|
|
||||||
let tasks = await user.get(`/tasks/challenge/${challenge._id}`);
|
let tasks = await user.get(`/tasks/challenge/${challenge._id}`);
|
||||||
|
|||||||
@@ -139,6 +139,23 @@ describe('PUT /tasks/:id', () => {
|
|||||||
expect(savedHabit.up).to.eql(false);
|
expect(savedHabit.up).to.eql(false);
|
||||||
expect(savedHabit.down).to.eql(false);
|
expect(savedHabit.down).to.eql(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('allows user to update their copy', async () => {
|
||||||
|
const userTasks = await user.get('/tasks/user');
|
||||||
|
const userChallengeTasks = userTasks.filter(task => task.challenge.id === challenge._id);
|
||||||
|
const userCopyOfChallengeTask = userChallengeTasks[0];
|
||||||
|
|
||||||
|
await user.put(`/tasks/${userCopyOfChallengeTask._id}`, {
|
||||||
|
notes: 'some new notes',
|
||||||
|
counterDown: 1,
|
||||||
|
counterUp: 2,
|
||||||
|
});
|
||||||
|
const savedHabit = await user.get(`/tasks/${userCopyOfChallengeTask._id}`);
|
||||||
|
|
||||||
|
expect(savedHabit.notes).to.eql('some new notes');
|
||||||
|
expect(savedHabit.counterDown).to.eql(1);
|
||||||
|
expect(savedHabit.counterUp).to.eql(2);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
context('todos', () => {
|
context('todos', () => {
|
||||||
|
|||||||
@@ -0,0 +1,189 @@
|
|||||||
|
import {
|
||||||
|
createAndPopulateGroup,
|
||||||
|
translate as t,
|
||||||
|
} from '../../../../../helpers/api-integration/v3';
|
||||||
|
import { find } from 'lodash';
|
||||||
|
|
||||||
|
describe('POST /tasks/:id/needs-work/:userId', () => {
|
||||||
|
let user, guild, member, member2, task;
|
||||||
|
|
||||||
|
function findAssignedTask (memberTask) {
|
||||||
|
return memberTask.group.id === guild._id;
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
let {group, members, groupLeader} = await createAndPopulateGroup({
|
||||||
|
groupDetails: {
|
||||||
|
name: 'Test Guild',
|
||||||
|
type: 'guild',
|
||||||
|
},
|
||||||
|
members: 2,
|
||||||
|
});
|
||||||
|
|
||||||
|
guild = group;
|
||||||
|
user = groupLeader;
|
||||||
|
member = members[0];
|
||||||
|
member2 = members[1];
|
||||||
|
|
||||||
|
task = await user.post(`/tasks/group/${guild._id}`, {
|
||||||
|
text: 'test todo',
|
||||||
|
type: 'todo',
|
||||||
|
requiresApproval: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('errors when user is not assigned', async () => {
|
||||||
|
await expect(user.post(`/tasks/${task._id}/needs-work/${member._id}`))
|
||||||
|
.to.eventually.be.rejected.and.to.eql({
|
||||||
|
code: 404,
|
||||||
|
error: 'NotFound',
|
||||||
|
message: t('taskNotFound'),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('errors when user is not the group leader', async () => {
|
||||||
|
await user.post(`/tasks/${task._id}/assign/${member._id}`);
|
||||||
|
await expect(member.post(`/tasks/${task._id}/needs-work/${member._id}`))
|
||||||
|
.to.eventually.be.rejected.and.to.eql({
|
||||||
|
code: 401,
|
||||||
|
error: 'NotAuthorized',
|
||||||
|
message: t('onlyGroupLeaderCanEditTasks'),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('marks as task as needing more work', async () => {
|
||||||
|
const initialNotifications = member.notifications.length;
|
||||||
|
|
||||||
|
await user.post(`/tasks/${task._id}/assign/${member._id}`);
|
||||||
|
|
||||||
|
let memberTasks = await member.get('/tasks/user');
|
||||||
|
let syncedTask = find(memberTasks, findAssignedTask);
|
||||||
|
|
||||||
|
// score task to require approval
|
||||||
|
await expect(member.post(`/tasks/${syncedTask._id}/score/up`))
|
||||||
|
.to.eventually.be.rejected.and.to.eql({
|
||||||
|
code: 401,
|
||||||
|
error: 'NotAuthorized',
|
||||||
|
message: t('taskApprovalHasBeenRequested'),
|
||||||
|
});
|
||||||
|
|
||||||
|
await user.post(`/tasks/${task._id}/needs-work/${member._id}`);
|
||||||
|
|
||||||
|
[memberTasks] = await Promise.all([member.get('/tasks/user'), member.sync()]);
|
||||||
|
syncedTask = find(memberTasks, findAssignedTask);
|
||||||
|
|
||||||
|
// Check that the notification approval request has been removed
|
||||||
|
expect(syncedTask.group.approval.requested).to.equal(false);
|
||||||
|
expect(syncedTask.group.approval.requestedDate).to.equal(undefined);
|
||||||
|
|
||||||
|
// Check that the notification is correct
|
||||||
|
expect(member.notifications.length).to.equal(initialNotifications + 1);
|
||||||
|
const notification = member.notifications[member.notifications.length - 1];
|
||||||
|
expect(notification.type).to.equal('GROUP_TASK_NEEDS_WORK');
|
||||||
|
|
||||||
|
const taskText = syncedTask.text;
|
||||||
|
const managerName = user.profile.name;
|
||||||
|
|
||||||
|
expect(notification.data.message).to.equal(t('taskNeedsWork', {taskText, managerName}));
|
||||||
|
|
||||||
|
expect(notification.data.task.id).to.equal(syncedTask._id);
|
||||||
|
expect(notification.data.task.text).to.equal(taskText);
|
||||||
|
|
||||||
|
expect(notification.data.group.id).to.equal(syncedTask.group.id);
|
||||||
|
expect(notification.data.group.name).to.equal(guild.name);
|
||||||
|
|
||||||
|
expect(notification.data.manager.id).to.equal(user._id);
|
||||||
|
expect(notification.data.manager.name).to.equal(managerName);
|
||||||
|
|
||||||
|
// Check that the managers' GROUP_TASK_APPROVAL notifications have been removed
|
||||||
|
await user.sync();
|
||||||
|
|
||||||
|
expect(user.notifications.find(n => {
|
||||||
|
n.data.taskId === syncedTask._id && n.type === 'GROUP_TASK_APPROVAL';
|
||||||
|
})).to.equal(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('allows a manager to mark a task as needing work', async () => {
|
||||||
|
await user.post(`/groups/${guild._id}/add-manager`, {
|
||||||
|
managerId: member2._id,
|
||||||
|
});
|
||||||
|
await member2.post(`/tasks/${task._id}/assign/${member._id}`);
|
||||||
|
|
||||||
|
let memberTasks = await member.get('/tasks/user');
|
||||||
|
let syncedTask = find(memberTasks, findAssignedTask);
|
||||||
|
|
||||||
|
// score task to require approval
|
||||||
|
await expect(member.post(`/tasks/${syncedTask._id}/score/up`))
|
||||||
|
.to.eventually.be.rejected.and.to.eql({
|
||||||
|
code: 401,
|
||||||
|
error: 'NotAuthorized',
|
||||||
|
message: t('taskApprovalHasBeenRequested'),
|
||||||
|
});
|
||||||
|
|
||||||
|
const initialNotifications = member.notifications.length;
|
||||||
|
|
||||||
|
await member2.post(`/tasks/${task._id}/needs-work/${member._id}`);
|
||||||
|
|
||||||
|
[memberTasks] = await Promise.all([member.get('/tasks/user'), member.sync()]);
|
||||||
|
syncedTask = find(memberTasks, findAssignedTask);
|
||||||
|
|
||||||
|
// Check that the notification approval request has been removed
|
||||||
|
expect(syncedTask.group.approval.requested).to.equal(false);
|
||||||
|
expect(syncedTask.group.approval.requestedDate).to.equal(undefined);
|
||||||
|
|
||||||
|
expect(member.notifications.length).to.equal(initialNotifications + 1);
|
||||||
|
const notification = member.notifications[member.notifications.length - 1];
|
||||||
|
expect(notification.type).to.equal('GROUP_TASK_NEEDS_WORK');
|
||||||
|
|
||||||
|
const taskText = syncedTask.text;
|
||||||
|
const managerName = member2.profile.name;
|
||||||
|
|
||||||
|
expect(notification.data.message).to.equal(t('taskNeedsWork', {taskText, managerName}));
|
||||||
|
|
||||||
|
expect(notification.data.task.id).to.equal(syncedTask._id);
|
||||||
|
expect(notification.data.task.text).to.equal(taskText);
|
||||||
|
|
||||||
|
expect(notification.data.group.id).to.equal(syncedTask.group.id);
|
||||||
|
expect(notification.data.group.name).to.equal(guild.name);
|
||||||
|
|
||||||
|
expect(notification.data.manager.id).to.equal(member2._id);
|
||||||
|
expect(notification.data.manager.name).to.equal(managerName);
|
||||||
|
|
||||||
|
// Check that the managers' GROUP_TASK_APPROVAL notifications have been removed
|
||||||
|
await Promise.all([user.sync(), member2.sync()]);
|
||||||
|
|
||||||
|
expect(user.notifications.find(n => {
|
||||||
|
n.data.taskId === syncedTask._id && n.type === 'GROUP_TASK_APPROVAL';
|
||||||
|
})).to.equal(undefined);
|
||||||
|
|
||||||
|
expect(member2.notifications.find(n => {
|
||||||
|
n.data.taskId === syncedTask._id && n.type === 'GROUP_TASK_APPROVAL';
|
||||||
|
})).to.equal(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('prevents marking a task as needing work if it was already approved', async () => {
|
||||||
|
await user.post(`/groups/${guild._id}/add-manager`, {
|
||||||
|
managerId: member2._id,
|
||||||
|
});
|
||||||
|
|
||||||
|
await member2.post(`/tasks/${task._id}/assign/${member._id}`);
|
||||||
|
await member2.post(`/tasks/${task._id}/approve/${member._id}`);
|
||||||
|
await expect(user.post(`/tasks/${task._id}/needs-work/${member._id}`))
|
||||||
|
.to.eventually.be.rejected.and.to.eql({
|
||||||
|
code: 401,
|
||||||
|
error: 'NotAuthorized',
|
||||||
|
message: t('canOnlyApproveTaskOnce'),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('prevents marking a task as needing work if it is not waiting for approval', async () => {
|
||||||
|
await user.post(`/tasks/${task._id}/assign/${member._id}`);
|
||||||
|
|
||||||
|
await expect(user.post(`/tasks/${task._id}/needs-work/${member._id}`))
|
||||||
|
.to.eventually.be.rejected.and.to.eql({
|
||||||
|
code: 401,
|
||||||
|
error: 'NotAuthorized',
|
||||||
|
message: t('taskApprovalWasNotRequested'),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -41,8 +41,9 @@ describe('POST /tasks/:id/score/:direction', () => {
|
|||||||
|
|
||||||
let memberTasks = await member.get('/tasks/user');
|
let memberTasks = await member.get('/tasks/user');
|
||||||
let syncedTask = find(memberTasks, findAssignedTask);
|
let syncedTask = find(memberTasks, findAssignedTask);
|
||||||
|
const direction = 'up';
|
||||||
|
|
||||||
await expect(member.post(`/tasks/${syncedTask._id}/score/up`))
|
await expect(member.post(`/tasks/${syncedTask._id}/score/${direction}`))
|
||||||
.to.eventually.be.rejected.and.to.eql({
|
.to.eventually.be.rejected.and.to.eql({
|
||||||
code: 401,
|
code: 401,
|
||||||
error: 'NotAuthorized',
|
error: 'NotAuthorized',
|
||||||
@@ -58,6 +59,7 @@ describe('POST /tasks/:id/score/:direction', () => {
|
|||||||
user: member.auth.local.username,
|
user: member.auth.local.username,
|
||||||
taskName: updatedTask.text,
|
taskName: updatedTask.text,
|
||||||
taskId: updatedTask._id,
|
taskId: updatedTask._id,
|
||||||
|
direction,
|
||||||
}, 'cs')); // This test only works if we have the notification translated
|
}, 'cs')); // This test only works if we have the notification translated
|
||||||
expect(user.notifications[1].data.groupId).to.equal(guild._id);
|
expect(user.notifications[1].data.groupId).to.equal(guild._id);
|
||||||
|
|
||||||
@@ -71,8 +73,9 @@ describe('POST /tasks/:id/score/:direction', () => {
|
|||||||
});
|
});
|
||||||
let memberTasks = await member.get('/tasks/user');
|
let memberTasks = await member.get('/tasks/user');
|
||||||
let syncedTask = find(memberTasks, findAssignedTask);
|
let syncedTask = find(memberTasks, findAssignedTask);
|
||||||
|
const direction = 'up';
|
||||||
|
|
||||||
await expect(member.post(`/tasks/${syncedTask._id}/score/up`))
|
await expect(member.post(`/tasks/${syncedTask._id}/score/${direction}`))
|
||||||
.to.eventually.be.rejected.and.to.eql({
|
.to.eventually.be.rejected.and.to.eql({
|
||||||
code: 401,
|
code: 401,
|
||||||
error: 'NotAuthorized',
|
error: 'NotAuthorized',
|
||||||
@@ -88,6 +91,7 @@ describe('POST /tasks/:id/score/:direction', () => {
|
|||||||
user: member.auth.local.username,
|
user: member.auth.local.username,
|
||||||
taskName: updatedTask.text,
|
taskName: updatedTask.text,
|
||||||
taskId: updatedTask._id,
|
taskId: updatedTask._id,
|
||||||
|
direction,
|
||||||
}));
|
}));
|
||||||
expect(user.notifications[1].data.groupId).to.equal(guild._id);
|
expect(user.notifications[1].data.groupId).to.equal(guild._id);
|
||||||
|
|
||||||
@@ -97,6 +101,7 @@ describe('POST /tasks/:id/score/:direction', () => {
|
|||||||
user: member.auth.local.username,
|
user: member.auth.local.username,
|
||||||
taskName: updatedTask.text,
|
taskName: updatedTask.text,
|
||||||
taskId: updatedTask._id,
|
taskId: updatedTask._id,
|
||||||
|
direction,
|
||||||
}));
|
}));
|
||||||
expect(member2.notifications[0].data.groupId).to.equal(guild._id);
|
expect(member2.notifications[0].data.groupId).to.equal(guild._id);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -75,15 +75,6 @@ describe('POST /tasks/:taskId/unassign/:memberId', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns error when non leader tries to create a task', async () => {
|
|
||||||
await expect(member.post(`/tasks/${task._id}/unassign/${member._id}`))
|
|
||||||
.to.eventually.be.rejected.and.eql({
|
|
||||||
code: 401,
|
|
||||||
error: 'NotAuthorized',
|
|
||||||
message: t('onlyGroupLeaderCanEditTasks'),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('unassigns a user from a task', async () => {
|
it('unassigns a user from a task', async () => {
|
||||||
await user.post(`/tasks/${task._id}/unassign/${member._id}`);
|
await user.post(`/tasks/${task._id}/unassign/${member._id}`);
|
||||||
|
|
||||||
@@ -129,4 +120,26 @@ describe('POST /tasks/:taskId/unassign/:memberId', () => {
|
|||||||
expect(groupTask[0].group.assignedUsers).to.not.contain(member._id);
|
expect(groupTask[0].group.assignedUsers).to.not.contain(member._id);
|
||||||
expect(syncedTask).to.not.exist;
|
expect(syncedTask).to.not.exist;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('allows a user to unassign themselves', async () => {
|
||||||
|
await member.post(`/tasks/${task._id}/unassign/${member._id}`);
|
||||||
|
|
||||||
|
let groupTask = await user.get(`/tasks/group/${guild._id}`);
|
||||||
|
let memberTasks = await member.get('/tasks/user');
|
||||||
|
let syncedTask = find(memberTasks, findAssignedTask);
|
||||||
|
|
||||||
|
expect(groupTask[0].group.assignedUsers).to.not.contain(member._id);
|
||||||
|
expect(syncedTask).to.not.exist;
|
||||||
|
});
|
||||||
|
|
||||||
|
// @TODO: Which do we want? The user to unassign themselves or not. This test was in
|
||||||
|
// here, but then we had a request to allow to unaissgn.
|
||||||
|
xit('returns error when non leader tries to unassign their a task', async () => {
|
||||||
|
await expect(member.post(`/tasks/${task._id}/unassign/${member._id}`))
|
||||||
|
.to.eventually.be.rejected.and.eql({
|
||||||
|
code: 401,
|
||||||
|
error: 'NotAuthorized',
|
||||||
|
message: t('onlyGroupLeaderCanEditTasks'),
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -86,6 +86,12 @@ describe('DELETE /user', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('deletes the user\'s tasks', async () => {
|
it('deletes the user\'s tasks', async () => {
|
||||||
|
await user.post('/tasks/user', {
|
||||||
|
text: 'test habit',
|
||||||
|
type: 'habit',
|
||||||
|
});
|
||||||
|
await user.sync();
|
||||||
|
|
||||||
// gets the user's tasks ids
|
// gets the user's tasks ids
|
||||||
let ids = [];
|
let ids = [];
|
||||||
each(user.tasksOrder, (idsForOrder) => {
|
each(user.tasksOrder, (idsForOrder) => {
|
||||||
@@ -302,7 +308,7 @@ describe('DELETE /user', () => {
|
|||||||
})).to.eventually.be.rejected.and.eql({
|
})).to.eventually.be.rejected.and.eql({
|
||||||
code: 401,
|
code: 401,
|
||||||
error: 'NotAuthorized',
|
error: 'NotAuthorized',
|
||||||
message: t('incorrectDeletePhrase'),
|
message: t('incorrectDeletePhrase', {magicWord: 'DELETE'}),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -27,4 +27,13 @@ describe('GET /user', () => {
|
|||||||
expect(returnedUser.auth.local.salt).to.not.exist;
|
expect(returnedUser.auth.local.salt).to.not.exist;
|
||||||
expect(returnedUser.apiToken).to.not.exist;
|
expect(returnedUser.apiToken).to.not.exist;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('returns only user properties requested', async () => {
|
||||||
|
let returnedUser = await user.get('/user?userFields=achievements,items.mounts');
|
||||||
|
|
||||||
|
expect(returnedUser._id).to.equal(user._id);
|
||||||
|
expect(returnedUser.achievements).to.exist;
|
||||||
|
expect(returnedUser.items.mounts).to.exist;
|
||||||
|
expect(returnedUser.stats).to.not.exist;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ describe('GET /user/anonymized', () => {
|
|||||||
'achievements.challenges': 'some',
|
'achievements.challenges': 'some',
|
||||||
'inbox.messages': [{ text: 'some text' }],
|
'inbox.messages': [{ text: 'some text' }],
|
||||||
tags: [{ name: 'some name', challenge: 'some challenge' }],
|
tags: [{ name: 'some name', challenge: 'some challenge' }],
|
||||||
|
notifications: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
await generateHabit({ userId: user._id });
|
await generateHabit({ userId: user._id });
|
||||||
@@ -65,6 +66,7 @@ describe('GET /user/anonymized', () => {
|
|||||||
expect(returnedUser.stats.toNextLevel).to.eql(common.tnl(user.stats.lvl));
|
expect(returnedUser.stats.toNextLevel).to.eql(common.tnl(user.stats.lvl));
|
||||||
expect(returnedUser.stats.maxMP).to.eql(30); // TODO why 30?
|
expect(returnedUser.stats.maxMP).to.eql(30); // TODO why 30?
|
||||||
expect(returnedUser.newMessages).to.not.exist;
|
expect(returnedUser.newMessages).to.not.exist;
|
||||||
|
expect(returnedUser.notifications).to.not.exist;
|
||||||
expect(returnedUser.profile).to.not.exist;
|
expect(returnedUser.profile).to.not.exist;
|
||||||
expect(returnedUser.purchased.plan).to.not.exist;
|
expect(returnedUser.purchased.plan).to.not.exist;
|
||||||
expect(returnedUser.contributor).to.not.exist;
|
expect(returnedUser.contributor).to.not.exist;
|
||||||
@@ -82,7 +84,7 @@ describe('GET /user/anonymized', () => {
|
|||||||
});
|
});
|
||||||
// tasks
|
// tasks
|
||||||
expect(tasks2).to.exist;
|
expect(tasks2).to.exist;
|
||||||
expect(tasks2.length).to.eql(5); // +1 because generateUser() assigns one todo
|
expect(tasks2.length).to.eql(5);
|
||||||
expect(tasks2[0].checklist).to.exist;
|
expect(tasks2[0].checklist).to.exist;
|
||||||
_.forEach(tasks2, (task) => {
|
_.forEach(tasks2, (task) => {
|
||||||
expect(task.text).to.eql('task text');
|
expect(task.text).to.eql('task text');
|
||||||
|
|||||||
@@ -1,67 +0,0 @@
|
|||||||
/* eslint-disable camelcase */
|
|
||||||
|
|
||||||
import {
|
|
||||||
generateUser,
|
|
||||||
translate as t,
|
|
||||||
} from '../../../../helpers/api-integration/v3';
|
|
||||||
import shared from '../../../../../website/common/script';
|
|
||||||
|
|
||||||
let content = shared.content;
|
|
||||||
|
|
||||||
describe('POST /user/buy/:key', () => {
|
|
||||||
let user;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
user = await generateUser({
|
|
||||||
'stats.gp': 400,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// More tests in common code unit tests
|
|
||||||
|
|
||||||
it('returns an error if the item is not found', async () => {
|
|
||||||
await expect(user.post('/user/buy/notExisting'))
|
|
||||||
.to.eventually.be.rejected.and.eql({
|
|
||||||
code: 404,
|
|
||||||
error: 'NotFound',
|
|
||||||
message: t('itemNotFound', {key: 'notExisting'}),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('buys a potion', async () => {
|
|
||||||
await user.update({
|
|
||||||
'stats.gp': 400,
|
|
||||||
'stats.hp': 40,
|
|
||||||
});
|
|
||||||
|
|
||||||
let potion = content.potion;
|
|
||||||
let res = await user.post('/user/buy/potion');
|
|
||||||
await user.sync();
|
|
||||||
|
|
||||||
expect(user.stats.hp).to.equal(50);
|
|
||||||
expect(res.data).to.eql(user.stats);
|
|
||||||
expect(res.message).to.equal(t('messageBought', {itemText: potion.text()}));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns an error if user tries to buy a potion with full health', async () => {
|
|
||||||
await user.update({
|
|
||||||
'stats.gp': 40,
|
|
||||||
'stats.hp': 50,
|
|
||||||
});
|
|
||||||
|
|
||||||
await expect(user.post('/user/buy/potion'))
|
|
||||||
.to.eventually.be.rejected.and.eql({
|
|
||||||
code: 401,
|
|
||||||
error: 'NotAuthorized',
|
|
||||||
message: t('messageHealthAlreadyMax'),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
it('buys a piece of gear', async () => {
|
|
||||||
let key = 'armor_warrior_1';
|
|
||||||
|
|
||||||
await user.post(`/user/buy/${key}`);
|
|
||||||
await user.sync();
|
|
||||||
|
|
||||||
expect(user.items.gear.owned.armor_warrior_1).to.eql(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
/* eslint-disable camelcase */
|
|
||||||
|
|
||||||
import {
|
|
||||||
generateUser,
|
|
||||||
translate as t,
|
|
||||||
} from '../../../../helpers/api-integration/v3';
|
|
||||||
|
|
||||||
describe('POST /user/buy-gear/:key', () => {
|
|
||||||
let user;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
user = await generateUser({
|
|
||||||
'stats.gp': 400,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// More tests in common code unit tests
|
|
||||||
|
|
||||||
it('returns an error if the item is not found', async () => {
|
|
||||||
await expect(user.post('/user/buy-gear/notExisting'))
|
|
||||||
.to.eventually.be.rejected.and.eql({
|
|
||||||
code: 404,
|
|
||||||
error: 'NotFound',
|
|
||||||
message: t('itemNotFound', {key: 'notExisting'}),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('buys a piece of gear', async () => {
|
|
||||||
let key = 'armor_warrior_1';
|
|
||||||
|
|
||||||
await user.post(`/user/buy-gear/${key}`);
|
|
||||||
await user.sync();
|
|
||||||
|
|
||||||
expect(user.items.gear.owned.armor_warrior_1).to.eql(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -13,15 +13,20 @@ describe('POST /user/open-mystery-item', () => {
|
|||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
user = await generateUser({
|
user = await generateUser({
|
||||||
'purchased.plan.mysteryItems': [mysteryItemKey],
|
'purchased.plan.mysteryItems': [mysteryItemKey],
|
||||||
|
notifications: [
|
||||||
|
{type: 'NEW_MYSTERY_ITEMS', data: { items: [mysteryItemKey] }},
|
||||||
|
],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// More tests in common code unit tests
|
// More tests in common code unit tests
|
||||||
|
|
||||||
it('opens a mystery item', async () => {
|
it('opens a mystery item', async () => {
|
||||||
|
expect(user.notifications.length).to.equal(1);
|
||||||
let response = await user.post('/user/open-mystery-item');
|
let response = await user.post('/user/open-mystery-item');
|
||||||
await user.sync();
|
await user.sync();
|
||||||
|
|
||||||
|
expect(user.notifications.length).to.equal(0);
|
||||||
expect(user.items.gear.owned[mysteryItemKey]).to.be.true;
|
expect(user.items.gear.owned[mysteryItemKey]).to.be.true;
|
||||||
expect(response.message).to.equal(t('mysteryItemOpened'));
|
expect(response.message).to.equal(t('mysteryItemOpened'));
|
||||||
expect(response.data.key).to.eql(mysteryItemKey);
|
expect(response.data.key).to.eql(mysteryItemKey);
|
||||||
|
|||||||
@@ -98,4 +98,24 @@ describe('POST /user/purchase/:type/:key', () => {
|
|||||||
await members[0].sync();
|
await members[0].sync();
|
||||||
expect(members[0].balance).to.equal(oldBalance);
|
expect(members[0].balance).to.equal(oldBalance);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('bulk purchasing', () => {
|
||||||
|
it('purchases a gem item', async () => {
|
||||||
|
await user.post(`/user/purchase/${type}/${key}`, {quantity: 2});
|
||||||
|
await user.sync();
|
||||||
|
|
||||||
|
expect(user.items[type][key]).to.equal(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can convert gold to gems if subscribed', async () => {
|
||||||
|
let oldBalance = user.balance;
|
||||||
|
await user.update({
|
||||||
|
'purchased.plan.customerId': 'group-plan',
|
||||||
|
'stats.gp': 1000,
|
||||||
|
});
|
||||||
|
await user.post('/user/purchase/gems/gem', {quantity: 2});
|
||||||
|
await user.sync();
|
||||||
|
expect(user.balance).to.equal(oldBalance + 0.50);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -26,13 +26,21 @@ describe('POST /user/read-card/:cardType', () => {
|
|||||||
await user.update({
|
await user.update({
|
||||||
'items.special.greetingReceived': [true],
|
'items.special.greetingReceived': [true],
|
||||||
'flags.cardReceived': true,
|
'flags.cardReceived': true,
|
||||||
|
notifications: [{
|
||||||
|
type: 'CARD_RECEIVED',
|
||||||
|
data: {card: cardType},
|
||||||
|
}],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await user.sync();
|
||||||
|
expect(user.notifications.length).to.equal(1);
|
||||||
|
|
||||||
let response = await user.post(`/user/read-card/${cardType}`);
|
let response = await user.post(`/user/read-card/${cardType}`);
|
||||||
await user.sync();
|
await user.sync();
|
||||||
|
|
||||||
expect(response.message).to.equal(t('readCard', {cardType}));
|
expect(response.message).to.equal(t('readCard', {cardType}));
|
||||||
expect(user.items.special[`${cardType}Received`]).to.be.empty;
|
expect(user.items.special[`${cardType}Received`]).to.be.empty;
|
||||||
expect(user.flags.cardReceived).to.be.false;
|
expect(user.flags.cardReceived).to.be.false;
|
||||||
|
expect(user.notifications.length).to.equal(0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
generateUser,
|
generateUser,
|
||||||
} from '../../../../helpers/api-integration/v3';
|
} from '../../../../helpers/api-integration/v3';
|
||||||
|
import { mockAnalyticsService as analytics } from '../../../../../website/server/libs/analyticsService';
|
||||||
|
|
||||||
describe('POST /user/sleep', () => {
|
describe('POST /user/sleep', () => {
|
||||||
let user;
|
let user;
|
||||||
@@ -22,4 +23,15 @@ describe('POST /user/sleep', () => {
|
|||||||
await user.sync();
|
await user.sync();
|
||||||
expect(user.preferences.sleep).to.be.false;
|
expect(user.preferences.sleep).to.be.false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('sends sleep status to analytics service', async () => {
|
||||||
|
sandbox.spy(analytics, 'track');
|
||||||
|
|
||||||
|
await user.post('/user/sleep');
|
||||||
|
await user.sync();
|
||||||
|
expect(analytics.track).to.be.calledOnce;
|
||||||
|
expect(analytics.track).to.be.calledWith('sleep', sandbox.match.has('status', user.preferences.sleep));
|
||||||
|
|
||||||
|
sandbox.restore();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user