mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-10-30 12:44:45 +01:00
Compare commits
1261 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
07e5bf1437 | ||
|
|
d2ca738256 | ||
|
|
f7983f39eb | ||
|
|
82d0e737a6 | ||
|
|
f8213aaf1b | ||
|
|
2335ad4167 | ||
|
|
88f28188a1 | ||
|
|
fce5be2e8f | ||
|
|
1d68cbfaa2 | ||
|
|
a44222a350 | ||
|
|
4d2510e322 | ||
|
|
3b5ed33e03 | ||
|
|
0a6bf92b6b | ||
|
|
05ae40bc2e | ||
|
|
57e96ea092 | ||
|
|
ea49b5b8e0 | ||
|
|
11a5de714a | ||
|
|
f74b4d3e73 | ||
|
|
ab6fdb99af | ||
|
|
e3efa557dd | ||
|
|
a6cea47789 | ||
|
|
5a200a88bd | ||
|
|
545499ea0b | ||
|
|
3e7f4229ec | ||
|
|
56d5f77b6e | ||
|
|
efdf5a2e16 | ||
|
|
add3a2887f | ||
|
|
0ed7c7596a | ||
|
|
55a452694f | ||
|
|
cd4d5f83ff | ||
|
|
8220199e49 | ||
|
|
bd1f6918ba | ||
|
|
5b21e62647 | ||
|
|
5e76d6df21 | ||
|
|
7348145b7d | ||
|
|
ba2832d21f | ||
|
|
688f5084a1 | ||
|
|
d5955b8889 | ||
|
|
01fd17ee3f | ||
|
|
979497dd35 | ||
|
|
1d3db244ba | ||
|
|
80ca074352 | ||
|
|
6d4f9e0759 | ||
|
|
d096695559 | ||
|
|
a2c8b8b05c | ||
|
|
8750701c08 | ||
|
|
900676bf0a | ||
|
|
e219daf44c | ||
|
|
1b12a6b51f | ||
|
|
8441b0a3d6 | ||
|
|
0667695390 | ||
|
|
4d322c1bf6 | ||
|
|
a855ddacc7 | ||
|
|
9d48ef7322 | ||
|
|
2991f7acfb | ||
|
|
9dbfb565bb | ||
|
|
f42e22b58f | ||
|
|
95f3315796 | ||
|
|
18ab57eb91 | ||
|
|
0dcbd8ccb8 | ||
|
|
ed82b46f7b | ||
|
|
dcc3044685 | ||
|
|
5cd62d7052 | ||
|
|
5e232d8c9f | ||
|
|
13793f8b3c | ||
|
|
55f875f95a | ||
|
|
14576be374 | ||
|
|
4cb2c26475 | ||
|
|
b66a0b76ef | ||
|
|
dd313b17b5 | ||
|
|
bb01475e02 | ||
|
|
757959529b | ||
|
|
71ad1957b1 | ||
|
|
3ba5ea1d2d | ||
|
|
902da35f2b | ||
|
|
aaa16a9527 | ||
|
|
b98e95ee45 | ||
|
|
757160d6b7 | ||
|
|
4cf68eb018 | ||
|
|
e91d5e5664 | ||
|
|
b9e12aca3e | ||
|
|
53ca9475ee | ||
|
|
e212842b50 | ||
|
|
d2f7cba43d | ||
|
|
84558f79d6 | ||
|
|
178e59f287 | ||
|
|
7acccc0763 | ||
|
|
8ac21d2fd4 | ||
|
|
7873800f87 | ||
|
|
727041f020 | ||
|
|
de0c62a37f | ||
|
|
68f420991e | ||
|
|
f211ebeb0a | ||
|
|
b91ef9f539 | ||
|
|
ac0601630e | ||
|
|
3239491144 | ||
|
|
c3b0a73507 | ||
|
|
b218eb2c00 | ||
|
|
37d9f76fea | ||
|
|
7f2e12ba23 | ||
|
|
21b43287e3 | ||
|
|
357b48dc8f | ||
|
|
2cbd78b139 | ||
|
|
ee5dd5842b | ||
|
|
074b8138de | ||
|
|
cd0b9c0a96 | ||
|
|
a09516944d | ||
|
|
b82660823d | ||
|
|
fde4402fbb | ||
|
|
5fe0776074 | ||
|
|
d218f316d3 | ||
|
|
f19e69948a | ||
|
|
e6807d36b5 | ||
|
|
1ece230621 | ||
|
|
d934d9d759 | ||
|
|
bf7fabb20a | ||
|
|
88a2f317d8 | ||
|
|
a30c4379a6 | ||
|
|
b509c6631d | ||
|
|
5a725fa4b0 | ||
|
|
dbe2143b7a | ||
|
|
7b687280d7 | ||
|
|
8db6c8bd4f | ||
|
|
bf91dacb94 | ||
|
|
33e0892e95 | ||
|
|
42b146d5d0 | ||
|
|
2bebaf2cf8 | ||
|
|
6181328ac1 | ||
|
|
c64d4b0914 | ||
|
|
f94fd0d69d | ||
|
|
2cd66436bc | ||
|
|
81a17738b8 | ||
|
|
b6b953ec46 | ||
|
|
ab34c83a9d | ||
|
|
9cea86f4e0 | ||
|
|
1b7a705bf9 | ||
|
|
e2c5b9058b | ||
|
|
cc751960ac | ||
|
|
433c73c9d3 | ||
|
|
680c2162a7 | ||
|
|
a2b38ffb02 | ||
|
|
6395870c00 | ||
|
|
9562ba432f | ||
|
|
8a3a83de37 | ||
|
|
745edd731d | ||
|
|
53b195931c | ||
|
|
37ae467fff | ||
|
|
d7d7d64b45 | ||
|
|
e76bdbd62d | ||
|
|
067e869141 | ||
|
|
51224a69d9 | ||
|
|
f56018d46a | ||
|
|
b846185f8a | ||
|
|
ff81e55839 | ||
|
|
9a3cdb5deb | ||
|
|
47ad7305f5 | ||
|
|
d9b5bbe2a9 | ||
|
|
c2fe04367f | ||
|
|
abcc77b7d6 | ||
|
|
07cbf45265 | ||
|
|
c035435476 | ||
|
|
89fdd8a8bb | ||
|
|
d406da4081 | ||
|
|
d74786ef85 | ||
|
|
64a3d08ce3 | ||
|
|
f635f178da | ||
|
|
1a7461a8a2 | ||
|
|
cc13c4f28e | ||
|
|
239f78674b | ||
|
|
d691dee2ca | ||
|
|
481bd6727d | ||
|
|
d0fc1e0751 | ||
|
|
b07dbb7752 | ||
|
|
34e7690c38 | ||
|
|
eca7382545 | ||
|
|
be95cd967a | ||
|
|
ce03f837c7 | ||
|
|
808885425f | ||
|
|
39a35f44ef | ||
|
|
2b2e1d4b9a | ||
|
|
869411c0e9 | ||
|
|
7484ecf729 | ||
|
|
3265440bc4 | ||
|
|
acf514e9cb | ||
|
|
2789d44dbf | ||
|
|
b579f31e9e | ||
|
|
5e781017ab | ||
|
|
b48f850eac | ||
|
|
5d6b6ed29a | ||
|
|
7fbc68511b | ||
|
|
ee2858199b | ||
|
|
b1dd79f75c | ||
|
|
1ac4dd8171 | ||
|
|
4f86abd6b2 | ||
|
|
23b0688abb | ||
|
|
38efe83cc7 | ||
|
|
2dadd74097 | ||
|
|
9f494360ef | ||
|
|
4122bbdecf | ||
|
|
256a3abc26 | ||
|
|
b7f3c0f389 | ||
|
|
dca00bf4b7 | ||
|
|
d31c8913d3 | ||
|
|
d839d57299 | ||
|
|
ccc3b4d337 | ||
|
|
7195ac15b9 | ||
|
|
a5ef6a129e | ||
|
|
38f5d63d29 | ||
|
|
43194b71ce | ||
|
|
e4a347a3cb | ||
|
|
7eaf3e04ab | ||
|
|
b6b03751c4 | ||
|
|
818d5e4eb6 | ||
|
|
f871c7cf63 | ||
|
|
e9eddec0c4 | ||
|
|
a48a6a292d | ||
|
|
12aef475c8 | ||
|
|
112e4e1d76 | ||
|
|
90eebbcd70 | ||
|
|
1ad9ba4e71 | ||
|
|
c42b72f8a8 | ||
|
|
830c8d3104 | ||
|
|
86ae5f3e44 | ||
|
|
3922415314 | ||
|
|
6c71abfac8 | ||
|
|
6ab08a7d52 | ||
|
|
dc46127fc7 | ||
|
|
b54f031acd | ||
|
|
eafa2f8cdd | ||
|
|
1815d2b6d3 | ||
|
|
14cba76ba8 | ||
|
|
fe45940d46 | ||
|
|
7dac53867b | ||
|
|
6f64cb7d9b | ||
|
|
7d989bcf50 | ||
|
|
7bd29c2dd7 | ||
|
|
9e10490102 | ||
|
|
5792bc0000 | ||
|
|
06812878b5 | ||
|
|
8714c7d162 | ||
|
|
e66f4e7812 | ||
|
|
c73f565f65 | ||
|
|
82e21df943 | ||
|
|
18ed148320 | ||
|
|
a5fc909f0d | ||
|
|
30a5192e19 | ||
|
|
79c0499672 | ||
|
|
dadb752087 | ||
|
|
37b29d3449 | ||
|
|
bb2ed249b9 | ||
|
|
bb90dde1b6 | ||
|
|
5299c8d406 | ||
|
|
8b81e38538 | ||
|
|
8e05a1b489 | ||
|
|
aafcbe60a3 | ||
|
|
56d1b77215 | ||
|
|
61da558a5d | ||
|
|
490531cc76 | ||
|
|
8cd4c502bc | ||
|
|
d8cacb653e | ||
|
|
59436a8bf7 | ||
|
|
6fade19f27 | ||
|
|
15d028a281 | ||
|
|
95b283676a | ||
|
|
6e7e81206a | ||
|
|
af74cc7c64 | ||
|
|
a9e2a17077 | ||
|
|
92057dbe17 | ||
|
|
b4ab525be5 | ||
|
|
d3c464d5ea | ||
|
|
804fe1c6d5 | ||
|
|
3d757c7814 | ||
|
|
3c5025a78e | ||
|
|
e028232527 | ||
|
|
e3b270a62e | ||
|
|
fceeacec3b | ||
|
|
f93c67e57c | ||
|
|
192dc26fbe | ||
|
|
1c1f270f64 | ||
|
|
483768f4a7 | ||
|
|
65031cef3a | ||
|
|
2fc1f46359 | ||
|
|
30fd530576 | ||
|
|
f79999fde7 | ||
|
|
90d6e443ba | ||
|
|
4ed1082558 | ||
|
|
00717eda76 | ||
|
|
d1b86e6c14 | ||
|
|
c813afba44 | ||
|
|
d49db6d367 | ||
|
|
d6835aec56 | ||
|
|
960f7b5886 | ||
|
|
cd9630332d | ||
|
|
ed21a37e5a | ||
|
|
16256ee190 | ||
|
|
ff57e31f4f | ||
|
|
6e21d154ae | ||
|
|
fdecc8ce16 | ||
|
|
3cc49f6637 | ||
|
|
47f49f4256 | ||
|
|
4f4bb52360 | ||
|
|
3748b3046b | ||
|
|
5cd0f56811 | ||
|
|
fe5beac91b | ||
|
|
52fd6a1451 | ||
|
|
ae445555e9 | ||
|
|
c4fc6671b4 | ||
|
|
e7a096158e | ||
|
|
98473fcfaa | ||
|
|
e4300fc714 | ||
|
|
456c5e57bc | ||
|
|
ffba435923 | ||
|
|
1f44444a50 | ||
|
|
185b20995a | ||
|
|
fdf2e590ea | ||
|
|
994123c387 | ||
|
|
273590716c | ||
|
|
6818a094ee | ||
|
|
c99855cef4 | ||
|
|
6845943ed0 | ||
|
|
044fe17757 | ||
|
|
ad0ede8d01 | ||
|
|
23815e89e1 | ||
|
|
061d990e39 | ||
|
|
71f4e6bc08 | ||
|
|
659f160e22 | ||
|
|
5f27bc5f90 | ||
|
|
074837b274 | ||
|
|
cfd19ac694 | ||
|
|
0897ab5dc9 | ||
|
|
5c6e8a7331 | ||
|
|
c576c5261e | ||
|
|
bbd98517ff | ||
|
|
392b54aa7b | ||
|
|
60b26d4ec0 | ||
|
|
aa517e0ad6 | ||
|
|
5ca489dee7 | ||
|
|
fe39ef72ff | ||
|
|
eee5f2f1df | ||
|
|
fd8572c28a | ||
|
|
f161987e1e | ||
|
|
2304d970a5 | ||
|
|
25ed05ab0a | ||
|
|
fa1fef11d6 | ||
|
|
6f5b9ef119 | ||
|
|
c64ea0a9a9 | ||
|
|
2e36b896d4 | ||
|
|
6fe73d431e | ||
|
|
998621cefe | ||
|
|
67bb179c25 | ||
|
|
c875861dab | ||
|
|
418c18ddb2 | ||
|
|
0caab5c8d0 | ||
|
|
218e65b04b | ||
|
|
fcd7ba77a7 | ||
|
|
b0d177643c | ||
|
|
c0e0b10a95 | ||
|
|
0bee2caf2e | ||
|
|
e56d097b3a | ||
|
|
8c63a9e31f | ||
|
|
28ed9d8bcc | ||
|
|
36ead77e0c | ||
|
|
e7969987ec | ||
|
|
97021e3422 | ||
|
|
218d47d64a | ||
|
|
bdfc23717e | ||
|
|
464cd87736 | ||
|
|
67a8eebb96 | ||
|
|
cfc0f6a3ac | ||
|
|
9f76db12bd | ||
|
|
70192e4935 | ||
|
|
5cd4ead9d1 | ||
|
|
87cd000bb8 | ||
|
|
0de5d8273b | ||
|
|
379898cc4d | ||
|
|
adeaa6c754 | ||
|
|
539f0e33e2 | ||
|
|
405e053377 | ||
|
|
52fbb8f899 | ||
|
|
c880596a77 | ||
|
|
a35f04be46 | ||
|
|
8682cf1cf7 | ||
|
|
6e922cfb44 | ||
|
|
cafabd93e1 | ||
|
|
1001d48eb7 | ||
|
|
b5c4618d56 | ||
|
|
92a4ba93d2 | ||
|
|
90d35d2f1f | ||
|
|
fead027cd2 | ||
|
|
5578426985 | ||
|
|
1c39fae127 | ||
|
|
45a757b589 | ||
|
|
8b610d771c | ||
|
|
bd81d27145 | ||
|
|
8eb430cbcb | ||
|
|
f218133d25 | ||
|
|
5f440d9097 | ||
|
|
0294868747 | ||
|
|
9c8d870d16 | ||
|
|
a7acd863f3 | ||
|
|
f32ef0a6ba | ||
|
|
ebf3b4aa47 | ||
|
|
5a8366468b | ||
|
|
df57518815 | ||
|
|
7d342b5115 | ||
|
|
388de9a97d | ||
|
|
28c79d9d20 | ||
|
|
85cf322b30 | ||
|
|
362ca73c94 | ||
|
|
5632031f16 | ||
|
|
90273362c4 | ||
|
|
7aadc10fab | ||
|
|
bfd45596b5 | ||
|
|
2eed4d38ae | ||
|
|
29bbe8534b | ||
|
|
9e008890b2 | ||
|
|
5505bf1e45 | ||
|
|
d40781ce07 | ||
|
|
d9719cdc05 | ||
|
|
8cc6a96be0 | ||
|
|
c5fb2d6506 | ||
|
|
afc336461e | ||
|
|
31376c8461 | ||
|
|
3a849bac18 | ||
|
|
563f3e2012 | ||
|
|
e24a024091 | ||
|
|
dc7d3816fd | ||
|
|
a094e13352 | ||
|
|
83376a38de | ||
|
|
db9c13a05d | ||
|
|
8c8aa78a1a | ||
|
|
6e3f7c005a | ||
|
|
1395380dfe | ||
|
|
833ceb3bf3 | ||
|
|
0522aa1551 | ||
|
|
58a9e4a439 | ||
|
|
84e2b2f45e | ||
|
|
71c0939a15 | ||
|
|
26c8323e70 | ||
|
|
bb7d447003 | ||
|
|
97ea510a34 | ||
|
|
99610b4916 | ||
|
|
9a43b85492 | ||
|
|
ecbf39cee4 | ||
|
|
4394772ee3 | ||
|
|
dd7fa73961 | ||
|
|
6ec23ce790 | ||
|
|
b953519e2d | ||
|
|
33a8072d23 | ||
|
|
213316d807 | ||
|
|
44cd4d0708 | ||
|
|
063b7a9af0 | ||
|
|
c08b5a4f1e | ||
|
|
90117625d7 | ||
|
|
1b3dad749e | ||
|
|
a622a3ebe3 | ||
|
|
2c83c16644 | ||
|
|
1034675184 | ||
|
|
a420876697 | ||
|
|
dc265e26b3 | ||
|
|
b5203dda61 | ||
|
|
80e92a8767 | ||
|
|
1c51e62e43 | ||
|
|
a265bfac9d | ||
|
|
92e4d5cd68 | ||
|
|
a18e9b3b18 | ||
|
|
c1a6ba6242 | ||
|
|
ed761a8b7b | ||
|
|
81d5971829 | ||
|
|
eb2d320d1f | ||
|
|
67538a368e | ||
|
|
d55b95834d | ||
|
|
9ff9cd3b35 | ||
|
|
b1f24de3c4 | ||
|
|
2ce2100f89 | ||
|
|
dbaae4183e | ||
|
|
2009bb97cb | ||
|
|
3fc9501bac | ||
|
|
2c2ded2b70 | ||
|
|
d689010e38 | ||
|
|
e173b7784c | ||
|
|
c3db59aae8 | ||
|
|
44e063c035 | ||
|
|
4e2c08cfed | ||
|
|
c845c337df | ||
|
|
418b57f9fb | ||
|
|
9725da258e | ||
|
|
4191ea1968 | ||
|
|
a9340ee60f | ||
|
|
c8d874d28a | ||
|
|
32a22f1545 | ||
|
|
8ffe302a49 | ||
|
|
5c50a40f39 | ||
|
|
84329e5fad | ||
|
|
64507a161e | ||
|
|
0f7fc27663 | ||
|
|
1545685a5b | ||
|
|
410355c3f1 | ||
|
|
ac27cabf6a | ||
|
|
972631e7ac | ||
|
|
d27ed7c406 | ||
|
|
031783b1d7 | ||
|
|
318aa7cbd9 | ||
|
|
f802a41f75 | ||
|
|
1d597039ca | ||
|
|
07bc374078 | ||
|
|
8153674dc0 | ||
|
|
0002148326 | ||
|
|
d198e23de6 | ||
|
|
4f4e141806 | ||
|
|
05e8d6f032 | ||
|
|
39847893d2 | ||
|
|
e4dbf09dda | ||
|
|
eb99b709e0 | ||
|
|
862b3453f8 | ||
|
|
7f48853d32 | ||
|
|
5c4f763bb1 | ||
|
|
bc9401b2f7 | ||
|
|
6fb9030b96 | ||
|
|
ba307af963 | ||
|
|
cf4b920a67 | ||
|
|
b0ff35a8f1 | ||
|
|
85b4c7825e | ||
|
|
5b7ea8ec5c | ||
|
|
5cfd0c863e | ||
|
|
10c6244c0c | ||
|
|
20e65be8bf | ||
|
|
8bac324ba7 | ||
|
|
2ee0288aaa | ||
|
|
b7ef4c50b2 | ||
|
|
52be9c750f | ||
|
|
b0200026aa | ||
|
|
e6c8b977c8 | ||
|
|
c78b5ecf7c | ||
|
|
f27e9b02d8 | ||
|
|
c06c19ca41 | ||
|
|
d5d894b8a9 | ||
|
|
7bd4e6a5a9 | ||
|
|
f13eed5663 | ||
|
|
a9a2fe6314 | ||
|
|
d6514bce8b | ||
|
|
c862bdb76a | ||
|
|
b596576c53 | ||
|
|
603fc8c4dd | ||
|
|
3c602351f9 | ||
|
|
29ed33461c | ||
|
|
fbc1044100 | ||
|
|
35e02d2871 | ||
|
|
6aa204c3f5 | ||
|
|
eaaa5ad7f3 | ||
|
|
54468ff499 | ||
|
|
53405aa586 | ||
|
|
7630c02e13 | ||
|
|
ec444384f4 | ||
|
|
cce9b33844 | ||
|
|
b977d42402 | ||
|
|
2672cbd790 | ||
|
|
f049d29d1b | ||
|
|
b7ca5be6ee | ||
|
|
5ae89761b0 | ||
|
|
8b0101c74c | ||
|
|
dbd295e35b | ||
|
|
b5428f4ac9 | ||
|
|
5b213b4f94 | ||
|
|
0142e332e8 | ||
|
|
b4f955333b | ||
|
|
696121fb24 | ||
|
|
2a7dfff88a | ||
|
|
2c921609c1 | ||
|
|
1f7dd421d4 | ||
|
|
45ca090105 | ||
|
|
02b22170e2 | ||
|
|
1134c7748b | ||
|
|
7019e32eed | ||
|
|
485c528b45 | ||
|
|
f1c1ba8efa | ||
|
|
b0e4c2cb11 | ||
|
|
0e346f7050 | ||
|
|
1eb1fe76a8 | ||
|
|
72a0e05804 | ||
|
|
5f37b9727a | ||
|
|
bf17b49046 | ||
|
|
36edf5265f | ||
|
|
6da243e034 | ||
|
|
b87ff03210 | ||
|
|
9d994f8a77 | ||
|
|
75c3f7214b | ||
|
|
f3f8fa3a42 | ||
|
|
2e34dab9a6 | ||
|
|
0f9b274059 | ||
|
|
041bde0cba | ||
|
|
8888e63005 | ||
|
|
7aa2fac14a | ||
|
|
4493e1d98c | ||
|
|
fcbc2acda7 | ||
|
|
729ba36ed3 | ||
|
|
896495cac5 | ||
|
|
3f89dae8c9 | ||
|
|
4ec5df170c | ||
|
|
fdbcd99525 | ||
|
|
99726bdc2f | ||
|
|
b00d1a067e | ||
|
|
0910ca7470 | ||
|
|
24f5e7c19f | ||
|
|
4f34443b84 | ||
|
|
714706f925 | ||
|
|
0899dddb42 | ||
|
|
f123fcd1b3 | ||
|
|
6ade7b08c8 | ||
|
|
c88b9b80b5 | ||
|
|
33149e1afa | ||
|
|
c8becbccb5 | ||
|
|
c9465cbfdd | ||
|
|
965b7a3be7 | ||
|
|
6b8784cf04 | ||
|
|
508d832d73 | ||
|
|
734e4a963f | ||
|
|
40495aaacb | ||
|
|
5566460541 | ||
|
|
774a1d9a96 | ||
|
|
60df912dcc | ||
|
|
7325bc0871 | ||
|
|
2fc233e70f | ||
|
|
9fd26a88ea | ||
|
|
76860fe3f8 | ||
|
|
b16e700de5 | ||
|
|
9205ec10b3 | ||
|
|
9aa4cce3b9 | ||
|
|
42d823ab44 | ||
|
|
f19331cfcc | ||
|
|
b7de7335ed | ||
|
|
2e00ec5534 | ||
|
|
f30d4b2cbf | ||
|
|
a3af39ed25 | ||
|
|
b57518732e | ||
|
|
a6a6aac400 | ||
|
|
48a92e77be | ||
|
|
a31f1f19fc | ||
|
|
d2a39a5124 | ||
|
|
9b69b640c3 | ||
|
|
7bead74b49 | ||
|
|
91e91788ce | ||
|
|
62c60ce520 | ||
|
|
423eafbd4d | ||
|
|
004ab51c46 | ||
|
|
f464403623 | ||
|
|
0fc66bef4e | ||
|
|
71636cd25e | ||
|
|
d5efb50d9b | ||
|
|
510e01effd | ||
|
|
b75e65f42d | ||
|
|
0e648d85a0 | ||
|
|
7b562c45cf | ||
|
|
b7a46637d5 | ||
|
|
284b2cc413 | ||
|
|
8b69540e71 | ||
|
|
8d9a4e97a8 | ||
|
|
f31a82c8f2 | ||
|
|
8bc02e82ee | ||
|
|
9040f9f04e | ||
|
|
ff82c37d5f | ||
|
|
37364b0700 | ||
|
|
11cfb3920a | ||
|
|
f5468d3771 | ||
|
|
99882d09ab | ||
|
|
7034d135d5 | ||
|
|
034c0c9bb5 | ||
|
|
e0711655f0 | ||
|
|
71e162eed5 | ||
|
|
8eac8732c5 | ||
|
|
896a1b74b6 | ||
|
|
3b36046a6a | ||
|
|
07991817e7 | ||
|
|
47b75156fa | ||
|
|
c630486fef | ||
|
|
0a070316b5 | ||
|
|
f6b34e85df | ||
|
|
2946f0df15 | ||
|
|
614d9a920a | ||
|
|
454524fb5b | ||
|
|
abc0777412 | ||
|
|
6972eb8f8f | ||
|
|
535ee2b2a7 | ||
|
|
c9755bee7c | ||
|
|
f810fff6fc | ||
|
|
5bbe59c52d | ||
|
|
3f52401384 | ||
|
|
e7944b3d98 | ||
|
|
08e925e3da | ||
|
|
16c9e42ad8 | ||
|
|
0e1d00c95f | ||
|
|
166f4683ca | ||
|
|
1fcc0d8d3a | ||
|
|
8a4c4e10f1 | ||
|
|
18ed0fe446 | ||
|
|
1f9ebeb629 | ||
|
|
b9d83122d1 | ||
|
|
d1f7e64156 | ||
|
|
0f8e7416f8 | ||
|
|
1c8b0f92df | ||
|
|
75e5b20f93 | ||
|
|
f9db432794 | ||
|
|
6cec7cbba2 | ||
|
|
f76d097313 | ||
|
|
59af471438 | ||
|
|
d0da303b7d | ||
|
|
596383e7a8 | ||
|
|
accba7fc13 | ||
|
|
dfc54f1600 | ||
|
|
b76ab58e3e | ||
|
|
6f093a94c4 | ||
|
|
a9a9e7a4ab | ||
|
|
b2058ec23d | ||
|
|
2461f53cf5 | ||
|
|
73fc288f3b | ||
|
|
ea78b6feb9 | ||
|
|
7c141614ed | ||
|
|
c072935e80 | ||
|
|
54e49ca3b9 | ||
|
|
b16a245d61 | ||
|
|
0a8109e496 | ||
|
|
cdfcc6419f | ||
|
|
9c25c2452f | ||
|
|
573f2e4732 | ||
|
|
81ffcf9c1b | ||
|
|
d8925a8811 | ||
|
|
217c16988b | ||
|
|
de7f953b67 | ||
|
|
8ee2a02e73 | ||
|
|
d9b573b430 | ||
|
|
cbcf5a03e1 | ||
|
|
487523f64b | ||
|
|
74cfc2cf52 | ||
|
|
2d489e870f | ||
|
|
6659d4fa52 | ||
|
|
5471af74fa | ||
|
|
6eb484605a | ||
|
|
8969b755a6 | ||
|
|
0062e5b1f1 | ||
|
|
50b98d8d92 | ||
|
|
7ddf4b1f7b | ||
|
|
c91da86b89 | ||
|
|
d549fea4ed | ||
|
|
a362914f93 | ||
|
|
61001d0e9a | ||
|
|
fb95d001ab | ||
|
|
bd5c4a08e2 | ||
|
|
34fb90455c | ||
|
|
d038d9f9bb | ||
|
|
420d7df4f5 | ||
|
|
3ee8072a6c | ||
|
|
50ebdd1ece | ||
|
|
4a80dcae2e | ||
|
|
bc03c1d18a | ||
|
|
e5d834b40a | ||
|
|
7f847d322f | ||
|
|
ed27ac15c8 | ||
|
|
88188e56d9 | ||
|
|
7170cf05d0 | ||
|
|
22a8d5e94c | ||
|
|
f37e5cde57 | ||
|
|
592cfef6c6 | ||
|
|
c1bd7f5dc5 | ||
|
|
8437b916c4 | ||
|
|
52e53aa466 | ||
|
|
4471186e09 | ||
|
|
6a2a844e04 | ||
|
|
122d147f07 | ||
|
|
912f00a652 | ||
|
|
627d4330c8 | ||
|
|
c7f6794dda | ||
|
|
00cb50a781 | ||
|
|
8be9964483 | ||
|
|
fd700f92ae | ||
|
|
f41665f5a9 | ||
|
|
8b385c0b7b | ||
|
|
282f8db933 | ||
|
|
662b08c242 | ||
|
|
7ea6c911cb | ||
|
|
97a069642d | ||
|
|
26e9827d39 | ||
|
|
88c625fe80 | ||
|
|
4044432fad | ||
|
|
6a767ed70b | ||
|
|
2c2ca4a9c8 | ||
|
|
380ad8c9e5 | ||
|
|
f53400f950 | ||
|
|
53c719acbd | ||
|
|
948a5d80c8 | ||
|
|
e1f141ee91 | ||
|
|
7a5a278dbb | ||
|
|
08ab4d5900 | ||
|
|
b8d5844d0f | ||
|
|
39b81aa685 | ||
|
|
44f196080c | ||
|
|
65bfd74c93 | ||
|
|
5e60a05cac | ||
|
|
59af4a2d3b | ||
|
|
8d273fac5e | ||
|
|
b58032d5fb | ||
|
|
db4123610f | ||
|
|
e4c1d96b59 | ||
|
|
fe1f0bf087 | ||
|
|
ccd0bec28c | ||
|
|
eebf38b5ae | ||
|
|
30cd738635 | ||
|
|
920b07ff12 | ||
|
|
a7db15d768 | ||
|
|
93768e70c5 | ||
|
|
3e29b958e3 | ||
|
|
ce1bcdeee0 | ||
|
|
971145a72a | ||
|
|
36595e0138 | ||
|
|
a1de566c34 | ||
|
|
2b8415fad0 | ||
|
|
2afbc23f6f | ||
|
|
a35c1954af | ||
|
|
47c488967c | ||
|
|
ee4a05d7ec | ||
|
|
308cd49e9c | ||
|
|
48e51a03d4 | ||
|
|
96f7a192d7 | ||
|
|
1e786412ba | ||
|
|
1739b83609 | ||
|
|
3606b58a1d | ||
|
|
202db599ae | ||
|
|
3aca0343e8 | ||
|
|
97b99c0550 | ||
|
|
0e63f68ed6 | ||
|
|
fa142e929f | ||
|
|
c4867f1e8e | ||
|
|
5f58fe66de | ||
|
|
a0e2d6a05e | ||
|
|
b67522e92b | ||
|
|
0e3496395c | ||
|
|
6e7b9f1f93 | ||
|
|
e6cf7564b8 | ||
|
|
bf424573a4 | ||
|
|
ac90a40be5 | ||
|
|
821f84dbe8 | ||
|
|
8fb67e7944 | ||
|
|
e81e458e9b | ||
|
|
aec23d32f3 | ||
|
|
4f2d066d66 | ||
|
|
eaf0c62e16 | ||
|
|
fc62db147f | ||
|
|
6c9ff3e8ed | ||
|
|
6ef45a7fd2 | ||
|
|
557212b549 | ||
|
|
f8bd116e54 | ||
|
|
9194e8226d | ||
|
|
e0140f67be | ||
|
|
1e2fc14db9 | ||
|
|
30082a3929 | ||
|
|
42d7744d12 | ||
|
|
2cbc41d02f | ||
|
|
01ce7712e3 | ||
|
|
c52e4a07d4 | ||
|
|
5212ac6394 | ||
|
|
7b5d6b508d | ||
|
|
c5a497ef91 | ||
|
|
54bee67e03 | ||
|
|
86ec68bedb | ||
|
|
8223563e76 | ||
|
|
37ab257f5b | ||
|
|
04d7ff13de | ||
|
|
25d07ac0ce | ||
|
|
724e1240a3 | ||
|
|
026e1a5bca | ||
|
|
6443918440 | ||
|
|
ac973ee753 | ||
|
|
c39b9dc320 | ||
|
|
2132a3a242 | ||
|
|
ba52a90d93 | ||
|
|
daa4994382 | ||
|
|
12034161b7 | ||
|
|
8438cf0578 | ||
|
|
614848d60b | ||
|
|
79087b27d3 | ||
|
|
5167f847d0 | ||
|
|
d3a0348ac7 | ||
|
|
de9883c3ac | ||
|
|
3d39718048 | ||
|
|
a0c51ee4ca | ||
|
|
b2edd1d932 | ||
|
|
6b5f46c5e1 | ||
|
|
ad191c2c5c | ||
|
|
4a55d36831 | ||
|
|
959adb05cf | ||
|
|
d114b858fd | ||
|
|
ae9db7aee3 | ||
|
|
10567d81e2 | ||
|
|
ba799c67f9 | ||
|
|
37b890f282 | ||
|
|
196e5f5b95 | ||
|
|
6db412f7e6 | ||
|
|
fa60c9a232 | ||
|
|
2c3d268a63 | ||
|
|
d4a80a8561 | ||
|
|
388492e1e7 | ||
|
|
8cd695c397 | ||
|
|
355f0fedfb | ||
|
|
38d78de4b3 | ||
|
|
6c64a1cd8c | ||
|
|
128ec5a1b1 | ||
|
|
d4d668f640 | ||
|
|
41ccd58f8e | ||
|
|
a33299a341 | ||
|
|
9129e22433 | ||
|
|
86d1bdaff1 | ||
|
|
206ed1f155 | ||
|
|
eb66e9ec2e | ||
|
|
8db99be017 | ||
|
|
c62386e2e5 | ||
|
|
042ac6ac73 | ||
|
|
a8655d923a | ||
|
|
bbbd1f9f73 | ||
|
|
8fee5a9ba0 | ||
|
|
8df2b1e8c2 | ||
|
|
24cceb1c91 | ||
|
|
21eac3cc94 | ||
|
|
e9ce968f88 | ||
|
|
8c283fdbe0 | ||
|
|
69a782a1db | ||
|
|
ccaf629228 | ||
|
|
147f2bb28e | ||
|
|
54a4bba228 | ||
|
|
6ee21dcfa9 | ||
|
|
68353fb874 | ||
|
|
68526c07ae | ||
|
|
5f319ca4f6 | ||
|
|
0d84643961 | ||
|
|
8470f16f4f | ||
|
|
1896a8fab0 | ||
|
|
891b5566a9 | ||
|
|
c83499545c | ||
|
|
4c837acf88 | ||
|
|
11b223a81e | ||
|
|
17001743e1 | ||
|
|
ac451bdb9b | ||
|
|
6af50c9f2f | ||
|
|
5231cb03a8 | ||
|
|
f8739b6f37 | ||
|
|
e31f62a818 | ||
|
|
d5d06c1d2d | ||
|
|
4fa2ef045d | ||
|
|
570a8bf0d5 | ||
|
|
b7dfe41e15 | ||
|
|
c26696a9eb | ||
|
|
f226b5da07 | ||
|
|
63cf5b6be7 | ||
|
|
f3a947339c | ||
|
|
1bb8acad5d | ||
|
|
8e04d6e284 | ||
|
|
f7415df6ba | ||
|
|
f85e1c2dc4 | ||
|
|
33628a0a6a | ||
|
|
5e6541faa6 | ||
|
|
c1ed02d383 | ||
|
|
3793e92b80 | ||
|
|
e3ce1c5322 | ||
|
|
84b16f28c2 | ||
|
|
9c702505a9 | ||
|
|
27c73e028a | ||
|
|
d125b8d2f8 | ||
|
|
451e08ce1c | ||
|
|
16b5b8b8c7 | ||
|
|
30a717148e | ||
|
|
bcf9670dbe | ||
|
|
129fccf646 | ||
|
|
9d755c5d5f | ||
|
|
05c43d1f9d | ||
|
|
45df73e4be | ||
|
|
eaa00598d0 | ||
|
|
88b14592c5 | ||
|
|
85136675e9 | ||
|
|
4e4181a394 | ||
|
|
f93822b0b3 | ||
|
|
a864e69042 | ||
|
|
2ccd9eaa1e | ||
|
|
5faf00d489 | ||
|
|
f211610f5d | ||
|
|
332f285ea2 | ||
|
|
006159cc9c | ||
|
|
3722452b51 | ||
|
|
d6b5d275da | ||
|
|
72073386ec | ||
|
|
d34ec62901 | ||
|
|
ca73b9af41 | ||
|
|
8b9bf88fa0 | ||
|
|
9133250a42 | ||
|
|
e60177f14a | ||
|
|
5f0ef2d8f0 | ||
|
|
770285f10d | ||
|
|
495dd2736c | ||
|
|
4467da980c | ||
|
|
082539b982 | ||
|
|
ef7719f91d | ||
|
|
f98efd4eb9 | ||
|
|
4a0856c919 | ||
|
|
2adc5c13e4 | ||
|
|
cf274310a8 | ||
|
|
a2ee73a2e2 | ||
|
|
c6c9503e22 | ||
|
|
403ac1ab7e | ||
|
|
63598f497b | ||
|
|
9fcc953b18 | ||
|
|
17408d01a9 | ||
|
|
ae786f28a2 | ||
|
|
1effa16b5b | ||
|
|
b7e601be16 | ||
|
|
6b7333927a | ||
|
|
31b439129d | ||
|
|
2de85b937f | ||
|
|
4f963e99dc | ||
|
|
58ce3a9a42 | ||
|
|
395676fcb1 | ||
|
|
cc4df1c995 | ||
|
|
48eada2c37 | ||
|
|
e45d0c9b80 | ||
|
|
84a20ef4f4 | ||
|
|
59a22805b9 | ||
|
|
95865f5ec8 | ||
|
|
79903d242f | ||
|
|
90959c18cd | ||
|
|
8b2019c292 | ||
|
|
9ab70ca276 | ||
|
|
d51aa25470 | ||
|
|
0b2c1e6d2e | ||
|
|
58ee6e9703 | ||
|
|
0044778497 | ||
|
|
46d6590fec | ||
|
|
b10f056a73 | ||
|
|
eeb890466a | ||
|
|
8d25a5d140 | ||
|
|
3b35a0a203 | ||
|
|
d787ad43d3 | ||
|
|
7d7fe6047c | ||
|
|
0ec1a91774 | ||
|
|
adf3281bef | ||
|
|
ea86b35833 | ||
|
|
ade14edcd7 | ||
|
|
3a1888739a | ||
|
|
3b54ce4949 | ||
|
|
4a8aaf7389 | ||
|
|
45eec47b7f | ||
|
|
4b9af8aa86 | ||
|
|
631bbcb786 | ||
|
|
76a10d6cf9 | ||
|
|
a1c9ebd661 | ||
|
|
9f06d78db6 | ||
|
|
ac98aa9271 | ||
|
|
455f7ac59b | ||
|
|
a42cb0e3ab | ||
|
|
d05d2fb9d7 | ||
|
|
6c4c5b4697 | ||
|
|
5da87640e4 | ||
|
|
fa044ffb44 | ||
|
|
5449652bd2 | ||
|
|
c12ae9ea25 | ||
|
|
734a300b92 | ||
|
|
1109ae308d | ||
|
|
8f1d241e83 | ||
|
|
acbca4d1dc | ||
|
|
1ea9be8aa2 | ||
|
|
ace02893e5 | ||
|
|
1c3e043fac | ||
|
|
71c9e7a685 | ||
|
|
fa945c7689 | ||
|
|
c54ce96033 | ||
|
|
85c4e93763 | ||
|
|
25e5e78373 | ||
|
|
06181d0a1a | ||
|
|
d5a8259fdb | ||
|
|
9db7141853 | ||
|
|
ec2a1927a0 | ||
|
|
1c1b0f00ad | ||
|
|
fb4d3e44d3 | ||
|
|
37fd062cf9 | ||
|
|
485c3c5c46 | ||
|
|
5007393f24 | ||
|
|
e111ac730c | ||
|
|
e7c78eabce | ||
|
|
5da7699548 | ||
|
|
f42955a0ba | ||
|
|
4d67df4da6 | ||
|
|
ab7459f4f3 | ||
|
|
469db7c0e2 | ||
|
|
952e813b30 | ||
|
|
f04d05fee1 | ||
|
|
6d9aa43c07 | ||
|
|
f527221079 | ||
|
|
d9b852e1ea | ||
|
|
a1207c1d8d | ||
|
|
f4fb90013d | ||
|
|
73a7c0eebc | ||
|
|
1819398f41 | ||
|
|
ab14312368 | ||
|
|
690d3e3fd2 | ||
|
|
36f9a4918f | ||
|
|
a4b5e27614 | ||
|
|
0abfe86296 | ||
|
|
e11c777325 | ||
|
|
63a04f36c9 | ||
|
|
e58af6e3ea | ||
|
|
6ba28b5757 | ||
|
|
ed607d2bae | ||
|
|
1f7fc594e5 | ||
|
|
45d0a4fac2 | ||
|
|
e50bc189aa | ||
|
|
95f8867bf8 | ||
|
|
4968b291f7 | ||
|
|
f7b9ca124d | ||
|
|
4623bcd877 | ||
|
|
4a368a1128 | ||
|
|
bec8cb01e0 | ||
|
|
f3c041a561 | ||
|
|
c21726ec61 | ||
|
|
df69208caa | ||
|
|
08d07cdd67 | ||
|
|
a309e48183 | ||
|
|
70c539cc81 | ||
|
|
11f136ac89 | ||
|
|
567d5f74ba | ||
|
|
338781f57b | ||
|
|
bd07f3cd38 | ||
|
|
0b735abd44 | ||
|
|
a88cdaf1fc | ||
|
|
7cae5f1a37 | ||
|
|
e453330535 | ||
|
|
b1e5fcdeaf | ||
|
|
10e0848a5c | ||
|
|
64e86bad91 | ||
|
|
21cf5d2321 | ||
|
|
a6106a801b | ||
|
|
769405ff34 | ||
|
|
a0803796b2 | ||
|
|
cb418882f3 | ||
|
|
b17a09ac17 | ||
|
|
88bb4f6a72 | ||
|
|
ae0c440846 | ||
|
|
2f69f4039e | ||
|
|
10370ea1dc | ||
|
|
0d65e5219e | ||
|
|
fed2d3fb19 | ||
|
|
6ec50ed0c1 | ||
|
|
6f1a551d76 | ||
|
|
bed97f0610 | ||
|
|
f86f98f4a6 | ||
|
|
0e442a0076 | ||
|
|
89f047b15b | ||
|
|
e64bc2e39a | ||
|
|
3b3fcbdfce | ||
|
|
558dd2e4bf | ||
|
|
7914a959b3 | ||
|
|
8a27524fa0 | ||
|
|
a64fed97ac | ||
|
|
e937d1722e | ||
|
|
f537e8142f | ||
|
|
e4b13eecd1 | ||
|
|
b5872a9577 | ||
|
|
48fa78bef2 | ||
|
|
781256c917 | ||
|
|
dcd680c293 | ||
|
|
ec6f53bb1b | ||
|
|
9834afee4a | ||
|
|
9b279563ea | ||
|
|
347fe69667 | ||
|
|
552cf70abd | ||
|
|
01c8ef9382 | ||
|
|
298a6a743c | ||
|
|
fa17ab7c17 | ||
|
|
0f339d8d3e | ||
|
|
99852fcd89 | ||
|
|
ca3d044aa1 | ||
|
|
b338e65dc9 | ||
|
|
1475c93962 | ||
|
|
ddec458364 | ||
|
|
bfe74a8dcb | ||
|
|
13d9da404d | ||
|
|
85e0af0c0e | ||
|
|
a2e5548b1c | ||
|
|
36dabad2c9 | ||
|
|
25b9e6f330 | ||
|
|
d0a786554c | ||
|
|
de79e0e3c3 | ||
|
|
0e74d25ede | ||
|
|
bde8b76da7 | ||
|
|
4235be4b43 | ||
|
|
3af7f89d10 | ||
|
|
396362d27e | ||
|
|
972efb1878 | ||
|
|
6c18d19d95 | ||
|
|
9b8bdb90d8 | ||
|
|
a84ea8b1b7 | ||
|
|
480a839bc5 | ||
|
|
df9c54fe20 | ||
|
|
8dbab1a976 | ||
|
|
df6088bc6d | ||
|
|
f0ff3a4eb6 | ||
|
|
786e4419da | ||
|
|
fff4ea3ad3 | ||
|
|
e18e89bc10 | ||
|
|
68ea28305d | ||
|
|
242b3508a1 | ||
|
|
8c316d939f | ||
|
|
45eb19e992 | ||
|
|
3ad0ffcaec | ||
|
|
fef8929dd9 | ||
|
|
911f894750 | ||
|
|
d4e2f5ac9e | ||
|
|
e43749bed1 | ||
|
|
8be29e27d0 | ||
|
|
301668fe22 | ||
|
|
767f3ebe12 | ||
|
|
25e6f8e26e | ||
|
|
1c6608d071 | ||
|
|
d2b160438c | ||
|
|
37d70f089c | ||
|
|
04b4912d59 | ||
|
|
b9a6d9ceec | ||
|
|
2a97915477 | ||
|
|
29a9deaeb8 | ||
|
|
0fcc1f2080 | ||
|
|
9287098e70 | ||
|
|
1812f63812 | ||
|
|
3d4107db3e | ||
|
|
67e750a81c | ||
|
|
7fe62a731b | ||
|
|
3c224fe353 | ||
|
|
bb6f465ac8 | ||
|
|
a2a79e4607 | ||
|
|
5125cc5f59 | ||
|
|
cb42a31c43 | ||
|
|
b1b1e512f5 | ||
|
|
5ba09c45df | ||
|
|
70dec611e3 | ||
|
|
2195464772 | ||
|
|
b662f8bdff | ||
|
|
0e6d6336c6 | ||
|
|
d8078adacd | ||
|
|
a88800df78 | ||
|
|
42e0095bbd | ||
|
|
ee1aa653f0 | ||
|
|
8d1b1ff794 | ||
|
|
70500d7c98 | ||
|
|
35849ebdd7 | ||
|
|
1332fd68b0 | ||
|
|
f9a47b1420 | ||
|
|
07b3824c4c | ||
|
|
92fa6805eb | ||
|
|
f64b34318f | ||
|
|
fe61c0f29e | ||
|
|
5f0b957dc2 | ||
|
|
37650ca674 | ||
|
|
b4dab2e13c | ||
|
|
b827b17481 | ||
|
|
27ef187e66 | ||
|
|
9c9b67aa9d | ||
|
|
7cff331800 | ||
|
|
e7bc505b88 |
8
.babelrc
8
.babelrc
@@ -1,10 +1,6 @@
|
|||||||
{
|
{
|
||||||
"presets": ["es2015"],
|
|
||||||
"plugins": [
|
"plugins": [
|
||||||
"transform-object-rest-spread",
|
"transform-es2015-modules-commonjs",
|
||||||
["transform-async-to-module-method", {
|
"syntax-object-rest-spread",
|
||||||
"module": "bluebird",
|
|
||||||
"method": "coroutine"
|
|
||||||
}]
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
4
.github/PULL_REQUEST_TEMPLATE.md
vendored
4
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1,7 +1,7 @@
|
|||||||
[//]: # (Note: See http://habitica.wikia.com/wiki/Using_Your_Local_Install_to_Modify_Habitica%27s_Website_and_API 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 # here, if applicable. This will automatically close the issue if your PR is merged in)
|
||||||
Fixes put_issue_url_here
|
Fixes put_#_and_issue_numer_here
|
||||||
|
|
||||||
### Changes
|
### Changes
|
||||||
[//]: # (Describe the changes that were made in detail here. Include pictures if necessary)
|
[//]: # (Describe the changes that were made in detail here. Include pictures if necessary)
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -39,6 +39,7 @@ dist-client
|
|||||||
test/client/unit/coverage
|
test/client/unit/coverage
|
||||||
test/client/e2e/reports
|
test/client/e2e/reports
|
||||||
test/client-old/spec/mocks/translations.js
|
test/client-old/spec/mocks/translations.js
|
||||||
|
yarn.lock
|
||||||
|
|
||||||
# Elastic Beanstalk Files
|
# Elastic Beanstalk Files
|
||||||
.elasticbeanstalk/*
|
.elasticbeanstalk/*
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ node_modules/**
|
|||||||
.bower-registry/**
|
.bower-registry/**
|
||||||
website/client-old/**
|
website/client-old/**
|
||||||
website/client/**
|
website/client/**
|
||||||
|
website/client/store/**
|
||||||
website/views/**
|
website/views/**
|
||||||
website/build/**
|
website/build/**
|
||||||
dist/**
|
dist/**
|
||||||
@@ -16,3 +17,4 @@ CHANGELOG.md
|
|||||||
newrelic_agent.log
|
newrelic_agent.log
|
||||||
*.swp
|
*.swp
|
||||||
*.swx
|
*.swx
|
||||||
|
website/raw_sprites/**
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
language: node_js
|
language: node_js
|
||||||
node_js:
|
node_js:
|
||||||
- '6'
|
- '10'
|
||||||
services:
|
services:
|
||||||
- mongodb
|
- mongodb
|
||||||
cache:
|
cache:
|
||||||
@@ -8,8 +8,6 @@ cache:
|
|||||||
- 'node_modules'
|
- 'node_modules'
|
||||||
addons:
|
addons:
|
||||||
chrome: stable
|
chrome: stable
|
||||||
before_install:
|
|
||||||
- npm install -g npm@5
|
|
||||||
before_script:
|
before_script:
|
||||||
- npm run test:build
|
- npm run test:build
|
||||||
- cp config.json.example config.json
|
- cp config.json.example config.json
|
||||||
@@ -22,8 +20,9 @@ env:
|
|||||||
- DISABLE_REQUEST_LOGGING=true
|
- DISABLE_REQUEST_LOGGING=true
|
||||||
matrix:
|
matrix:
|
||||||
- TEST="lint"
|
- TEST="lint"
|
||||||
- TEST="test:api-v3:unit" REQUIRES_SERVER=true COVERAGE=true
|
- TEST="test:api:unit" REQUIRES_SERVER=true COVERAGE=true
|
||||||
- TEST="test:api-v3:integration" REQUIRES_SERVER=true COVERAGE=true
|
- TEST="test:api-v3:integration" REQUIRES_SERVER=true COVERAGE=true
|
||||||
|
- TEST="test:api-v4:integration" REQUIRES_SERVER=true COVERAGE=true
|
||||||
- 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
|
||||||
|
|||||||
50
Dockerfile
50
Dockerfile
@@ -1,21 +1,29 @@
|
|||||||
FROM node:boron
|
FROM node:10
|
||||||
|
|
||||||
# Upgrade NPM to v5 (Yarn is needed because of this bug https://github.com/npm/npm/issues/16807)
|
ENV ADMIN_EMAIL admin@habitica.com
|
||||||
# The used solution is suggested here https://github.com/npm/npm/issues/16807#issuecomment-313591975
|
ENV AMAZON_PAYMENTS_CLIENT_ID amzn1.application-oa2-client.68ed9e6904ef438fbc1bf86bf494056e
|
||||||
RUN yarn global add npm@5
|
ENV AMAZON_PAYMENTS_SELLER_ID AMQ3SB4SG5E91
|
||||||
# Install global packages
|
ENV AMPLITUDE_KEY e8d4c24b3d6ef3ee73eeba715023dd43
|
||||||
RUN npm install -g gulp-cli mocha
|
ENV BASE_URL https://habitica.com
|
||||||
|
ENV FACEBOOK_KEY 128307497299777
|
||||||
# Clone Habitica repo and install dependencies
|
ENV GA_ID UA-33510635-1
|
||||||
RUN mkdir -p /usr/src/habitrpg
|
ENV GOOGLE_CLIENT_ID 1035232791481-32vtplgnjnd1aufv3mcu1lthf31795fq.apps.googleusercontent.com
|
||||||
WORKDIR /usr/src/habitrpg
|
ENV NODE_ENV production
|
||||||
RUN git clone https://github.com/HabitRPG/habitica.git /usr/src/habitrpg
|
ENV STRIPE_PUB_KEY pk_85fQ0yMECHNfHTSsZoxZXlPSwSNfA
|
||||||
RUN cp config.json.example config.json
|
|
||||||
RUN npm install
|
# Install global packages
|
||||||
|
RUN npm install -g gulp-cli mocha
|
||||||
# Create Build dir
|
|
||||||
RUN mkdir -p ./website/build
|
# Clone Habitica repo and install dependencies
|
||||||
|
RUN mkdir -p /usr/src/habitrpg
|
||||||
# Start Habitica
|
WORKDIR /usr/src/habitrpg
|
||||||
EXPOSE 3000
|
RUN git clone --branch release https://github.com/HabitRPG/habitica.git /usr/src/habitrpg
|
||||||
CMD ["npm", "start"]
|
RUN npm install
|
||||||
|
RUN gulp build:prod --force
|
||||||
|
|
||||||
|
# Create Build dir
|
||||||
|
RUN mkdir -p ./website/build
|
||||||
|
|
||||||
|
# Start Habitica
|
||||||
|
EXPOSE 3000
|
||||||
|
CMD ["node", "./website/transpiled-babel/index.js"]
|
||||||
|
|||||||
18
Dockerfile-Dev
Normal file
18
Dockerfile-Dev
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
FROM node:10
|
||||||
|
|
||||||
|
# Install global packages
|
||||||
|
RUN npm install -g gulp-cli mocha
|
||||||
|
|
||||||
|
# Clone Habitica repo and install dependencies
|
||||||
|
RUN mkdir -p /usr/src/habitrpg
|
||||||
|
WORKDIR /usr/src/habitrpg
|
||||||
|
RUN git clone https://github.com/HabitRPG/habitica.git /usr/src/habitrpg
|
||||||
|
RUN cp config.json.example config.json
|
||||||
|
RUN npm install
|
||||||
|
|
||||||
|
# Create Build dir
|
||||||
|
RUN mkdir -p ./website/build
|
||||||
|
|
||||||
|
# Start Habitica
|
||||||
|
EXPOSE 3000
|
||||||
|
CMD ["npm", "start"]
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
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
|
|
||||||
RUN npm install -g gulp-cli mocha
|
|
||||||
|
|
||||||
# Clone Habitica repo and install dependencies
|
|
||||||
RUN mkdir -p /usr/src/habitrpg
|
|
||||||
WORKDIR /usr/src/habitrpg
|
|
||||||
RUN git clone --branch v4.29.7 https://github.com/HabitRPG/habitica.git /usr/src/habitrpg
|
|
||||||
RUN npm install
|
|
||||||
RUN gulp build:prod --force
|
|
||||||
|
|
||||||
# Create Build dir
|
|
||||||
RUN mkdir -p ./website/build
|
|
||||||
|
|
||||||
# Start Habitica
|
|
||||||
EXPOSE 3000
|
|
||||||
CMD ["node", "./website/transpiled-babel/index.js"]
|
|
||||||
@@ -10,4 +10,3 @@ We need more programmers! Your assistance will be greatly appreciated.
|
|||||||
For an introduction to the technologies used and how the software is organized, refer to [Guidance for Blacksmiths](http://habitica.wikia.com/wiki/Guidance_for_Blacksmiths).
|
For an introduction to the technologies used and how the software is organized, refer to [Guidance for Blacksmiths](http://habitica.wikia.com/wiki/Guidance_for_Blacksmiths).
|
||||||
|
|
||||||
To set up a local install of Habitica for development and testing on various platforms, see [Setting up Habitica Locally](http://habitica.wikia.com/wiki/Setting_up_Habitica_Locally).
|
To set up a local install of Habitica for development and testing on various platforms, see [Setting up Habitica Locally](http://habitica.wikia.com/wiki/Setting_up_Habitica_Locally).
|
||||||
|
|
||||||
|
|||||||
@@ -1,116 +1,81 @@
|
|||||||
{
|
{
|
||||||
"PORT":3000,
|
"ADMIN_EMAIL": "you@example.com",
|
||||||
"ENABLE_CONSOLE_LOGS_IN_PROD":"false",
|
"AMAZON_PAYMENTS_CLIENT_ID": "CLIENT_ID",
|
||||||
"IP":"0.0.0.0",
|
"AMAZON_PAYMENTS_MODE": "sandbox",
|
||||||
"WEB_CONCURRENCY":1,
|
"AMAZON_PAYMENTS_MWS_KEY": "MWS_KEY",
|
||||||
"BASE_URL":"http://localhost:3000",
|
"AMAZON_PAYMENTS_MWS_SECRET": "MWS_SECRET",
|
||||||
"FACEBOOK_KEY":"123456789012345",
|
"AMAZON_PAYMENTS_SELLER_ID": "SELLER_ID",
|
||||||
"FACEBOOK_SECRET":"aaaabbbbccccddddeeeeffff00001111",
|
"AMPLITUDE_KEY": "AMPLITUDE_KEY",
|
||||||
"GOOGLE_CLIENT_ID":"123456789012345",
|
"AMPLITUDE_SECRET": "AMPLITUDE_SECRET",
|
||||||
"GOOGLE_CLIENT_SECRET":"aaaabbbbccccddddeeeeffff00001111",
|
"BASE_URL": "http://localhost:3000",
|
||||||
"PLAY_API": {
|
"CRON_SAFE_MODE": "false",
|
||||||
"CLIENT_ID": "aaaabbbbccccddddeeeeffff00001111",
|
"CRON_SEMI_SAFE_MODE": "false",
|
||||||
"CLIENT_SECRET": "aaaabbbbccccddddeeeeffff00001111",
|
"DISABLE_REQUEST_LOGGING": "true",
|
||||||
"ACCESS_TOKEN":"aaaabbbbccccddddeeeeffff00001111",
|
"EMAILS_COMMUNITY_MANAGER_EMAIL": "admin@habitica.com",
|
||||||
"REFRESH_TOKEN":"aaaabbbbccccddddeeeeffff00001111"
|
"EMAILS_PRESS_ENQUIRY_EMAIL": "admin@habitica.com",
|
||||||
},
|
"EMAILS_TECH_ASSISTANCE_EMAIL": "admin@habitica.com",
|
||||||
"NODE_DB_URI":"mongodb://localhost/habitrpg",
|
"EMAIL_SERVER_AUTH_PASSWORD": "password",
|
||||||
"TEST_DB_URI":"mongodb://localhost/habitrpg_test",
|
"EMAIL_SERVER_AUTH_USER": "user",
|
||||||
"NODE_ENV":"development",
|
"EMAIL_SERVER_URL": "http://example.com",
|
||||||
"ENABLE_CONSOLE_LOGS_IN_TEST": false,
|
"ENABLE_CONSOLE_LOGS_IN_PROD": "false",
|
||||||
"CRON_SAFE_MODE":"false",
|
"ENABLE_CONSOLE_LOGS_IN_TEST": "false",
|
||||||
"CRON_SEMI_SAFE_MODE":"false",
|
"FACEBOOK_KEY": "123456789012345",
|
||||||
"MAINTENANCE_MODE": "false",
|
"FACEBOOK_SECRET": "aaaabbbbccccddddeeeeffff00001111",
|
||||||
"SESSION_SECRET":"YOUR SECRET HERE",
|
"FLAG_REPORT_EMAIL": "email@example.com, email2@example.com",
|
||||||
"SESSION_SECRET_KEY": "1234567891234567891234567891234567891234567891234567891234567891",
|
"GA_ID": "GA_ID",
|
||||||
"SESSION_SECRET_IV": "12345678912345678912345678912345",
|
"GOOGLE_CLIENT_ID": "123456789012345",
|
||||||
"ADMIN_EMAIL": "you@example.com",
|
"GOOGLE_CLIENT_SECRET": "aaaabbbbccccddddeeeeffff00001111",
|
||||||
"SMTP_USER":"user@example.com",
|
"IAP_GOOGLE_KEYDIR": "/path/to/google/public/key/dir/",
|
||||||
"SMTP_PASS":"password",
|
"IGNORE_REDIRECT": "true",
|
||||||
"SMTP_SERVICE":"Gmail",
|
"ITUNES_SHARED_SECRET": "aaaabbbbccccddddeeeeffff00001111",
|
||||||
"SMTP_HOST":"example.com",
|
"LOGGLY_CLIENT_TOKEN": "token",
|
||||||
"SMTP_PORT": 587,
|
"LOGGLY_SUBDOMAIN": "example-subdomain",
|
||||||
"SMTP_TLS": true,
|
"LOGGLY_TOKEN": "example-token",
|
||||||
"STRIPE_API_KEY":"aaaabbbbccccddddeeeeffff00001111",
|
"MAINTENANCE_MODE": "false",
|
||||||
"STRIPE_PUB_KEY":"22223333444455556666777788889999",
|
"NODE_DB_URI": "mongodb://localhost/habitrpg",
|
||||||
"NEW_RELIC_LICENSE_KEY":"NEW_RELIC_LICENSE_KEY",
|
"NODE_ENV": "development",
|
||||||
"NEW_RELIC_NO_CONFIG_FILE":"true",
|
"PATH": "bin:node_modules/.bin:/usr/local/bin:/usr/bin:/bin",
|
||||||
"NEW_RELIC_APPLICATION_ID":"NEW_RELIC_APPLICATION_ID",
|
"PAYPAL_BILLING_PLANS_basic_12mo": "basic_12mo",
|
||||||
"NEW_RELIC_API_KEY":"NEW_RELIC_API_KEY",
|
"PAYPAL_BILLING_PLANS_basic_3mo": "basic_3mo",
|
||||||
"GA_ID": "GA_ID",
|
"PAYPAL_BILLING_PLANS_basic_6mo": "basic_6mo",
|
||||||
"AMPLITUDE_KEY": "AMPLITUDE_KEY",
|
"PAYPAL_BILLING_PLANS_basic_earned": "basic_earned",
|
||||||
"AMAZON_PAYMENTS": {
|
"PAYPAL_BILLING_PLANS_google_6mo": "google_6mo",
|
||||||
"SELLER_ID": "SELLER_ID",
|
"PAYPAL_CLIENT_ID": "client_id",
|
||||||
"CLIENT_ID": "CLIENT_ID",
|
"PAYPAL_CLIENT_SECRET": "client_secret",
|
||||||
"MWS_KEY": "",
|
"PAYPAL_EXPERIENCE_PROFILE_ID": "xp_profile_id",
|
||||||
"MWS_SECRET": ""
|
"PAYPAL_MODE": "sandbox",
|
||||||
},
|
"PLAY_API_ACCESS_TOKEN": "aaaabbbbccccddddeeeeffff00001111",
|
||||||
"FLAG_REPORT_EMAIL": "email@mod.com,email2@mod.com",
|
"PLAY_API_CLIENT_ID": "aaaabbbbccccddddeeeeffff00001111",
|
||||||
"EMAIL_SERVER": {
|
"PLAY_API_CLIENT_SECRET": "aaaabbbbccccddddeeeeffff00001111",
|
||||||
"url": "http://example.com",
|
"PLAY_API_REFRESH_TOKEN": "aaaabbbbccccddddeeeeffff00001111",
|
||||||
"authUser": "user",
|
"PORT": 3000,
|
||||||
"authPassword": "password"
|
"PUSH_CONFIGS_APN_ENABLED": "false",
|
||||||
},
|
"PUSH_CONFIGS_APN_KEY": "xxxxxxxxxx",
|
||||||
"S3":{
|
"PUSH_CONFIGS_APN_KEY_ID": "xxxxxxxxxx",
|
||||||
"bucket":"bucket",
|
"PUSH_CONFIGS_APN_TEAM_ID": "aaabbbcccd",
|
||||||
"accessKeyId":"accessKeyId",
|
"PUSH_CONFIGS_FCM_SERVER_API_KEY": "aaabbbcccd",
|
||||||
"secretAccessKey":"secretAccessKey"
|
"S3_ACCESS_KEY_ID": "accessKeyId",
|
||||||
},
|
"S3_BUCKET": "bucket",
|
||||||
"SLACK_URL": "https://hooks.slack.com/services/some-url",
|
"S3_SECRET_ACCESS_KEY": "secretAccessKey",
|
||||||
"TRANSIFEX_SLACK_CHANNEL": "transifex",
|
"SESSION_SECRET": "YOUR SECRET HERE",
|
||||||
"PAYPAL":{
|
"SESSION_SECRET_IV": "12345678912345678912345678912345",
|
||||||
"billing_plans": {
|
"SESSION_SECRET_KEY": "1234567891234567891234567891234567891234567891234567891234567891",
|
||||||
"basic_earned":"basic_earned",
|
"SITE_HTTP_AUTH_ENABLED": "false",
|
||||||
"basic_3mo":"basic_3mo",
|
"SITE_HTTP_AUTH_PASSWORD": "password",
|
||||||
"basic_6mo":"basic_6mo",
|
"SITE_HTTP_AUTH_USERNAME": "admin",
|
||||||
"google_6mo":"google_6mo",
|
"SLACK_FLAGGING_FOOTER_LINK": "https://habitrpg.github.io/flag-o-rama/",
|
||||||
"basic_12mo":"basic_12mo"
|
"SLACK_FLAGGING_URL": "https://hooks.slack.com/services/id/id/id",
|
||||||
},
|
"SLACK_SUBSCRIPTIONS_URL": "https://hooks.slack.com/services/id/id/id",
|
||||||
"mode":"sandbox",
|
"SLACK_URL": "https://hooks.slack.com/services/some-url",
|
||||||
"client_id":"client_id",
|
"SMTP_HOST": "example.com",
|
||||||
"client_secret":"client_secret",
|
"SMTP_PASS": "password",
|
||||||
"experience_profile_id": ""
|
"SMTP_PORT": 587,
|
||||||
},
|
"SMTP_SERVICE": "Gmail",
|
||||||
"IAP_GOOGLE_KEYDIR": "/path/to/google/public/key/dir/",
|
"SMTP_TLS": "true",
|
||||||
"LOGGLY_TOKEN": "token",
|
"SMTP_USER": "user@example.com",
|
||||||
"LOGGLY_CLIENT_TOKEN": "token",
|
"STRIPE_API_KEY": "aaaabbbbccccddddeeeeffff00001111",
|
||||||
"LOGGLY_ACCOUNT": "account",
|
"STRIPE_PUB_KEY": "22223333444455556666777788889999",
|
||||||
"PUSH_CONFIGS": {
|
"TEST_DB_URI": "mongodb://localhost/habitrpg_test",
|
||||||
"GCM_SERVER_API_KEY": "",
|
"TRANSIFEX_SLACK_CHANNEL": "transifex",
|
||||||
"APN_ENABLED": "false",
|
"WEB_CONCURRENCY": 1
|
||||||
"FCM_SERVER_API_KEY": ""
|
|
||||||
},
|
|
||||||
"SITE_HTTP_AUTH": {
|
|
||||||
"ENABLED": "false",
|
|
||||||
"USERNAME": "admin",
|
|
||||||
"PASSWORD": "password"
|
|
||||||
},
|
|
||||||
"PUSHER": {
|
|
||||||
"ENABLED": "false",
|
|
||||||
"APP_ID": "appId",
|
|
||||||
"KEY": "key",
|
|
||||||
"SECRET": "secret"
|
|
||||||
},
|
|
||||||
"SLACK": {
|
|
||||||
"FLAGGING_URL": "https://hooks.slack.com/services/id/id/id",
|
|
||||||
"FLAGGING_FOOTER_LINK": "https://habitrpg.github.io/flag-o-rama/",
|
|
||||||
"SUBSCRIPTIONS_URL": "https://hooks.slack.com/services/id/id/id"
|
|
||||||
},
|
|
||||||
"ITUNES_SHARED_SECRET": "aaaabbbbccccddddeeeeffff00001111",
|
|
||||||
"EMAILS" : {
|
|
||||||
"COMMUNITY_MANAGER_EMAIL" : "leslie@habitica.com",
|
|
||||||
"TECH_ASSISTANCE_EMAIL" : "admin@habitica.com",
|
|
||||||
"PRESS_ENQUIRY_EMAIL" : "leslie@habitica.com"
|
|
||||||
},
|
|
||||||
"LOGGLY" : {
|
|
||||||
"TOKEN" : "example-token",
|
|
||||||
"SUBDOMAIN" : "exmaple-subdomain"
|
|
||||||
},
|
|
||||||
"KAFKA": {
|
|
||||||
"GROUP_ID": "",
|
|
||||||
"CLOUDKARAFKA_BROKERS": "",
|
|
||||||
"CLOUDKARAFKA_USERNAME": "",
|
|
||||||
"CLOUDKARAFKA_PASSWORD": "",
|
|
||||||
"CLOUDKARAFKA_TOPIC_PREFIX": ""
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
100
database_reports/20180607_subscriber_histories.js
Normal file
100
database_reports/20180607_subscriber_histories.js
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
import max from 'lodash/max';
|
||||||
|
import mean from 'lodash/mean';
|
||||||
|
import monk from 'monk';
|
||||||
|
import round from 'lodash/round';
|
||||||
|
import sum from 'lodash/sum';
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Output data on subscribers' task histories, formatted for CSV.
|
||||||
|
* User ID,Count of Dailies,Count of Habits,Total History Size,Max History Size,Mean History Size,Median History Size
|
||||||
|
*/
|
||||||
|
const connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
|
||||||
|
|
||||||
|
let dbUsers = monk(connectionString).get('users', { castIds: false });
|
||||||
|
let dbTasks = monk(connectionString).get('tasks', { castIds: false });
|
||||||
|
|
||||||
|
function usersReport () {
|
||||||
|
let allHistoryLengths = [];
|
||||||
|
|
||||||
|
console.info('User ID,Count of Dailies,Count of Habits,Total History Size,Max History Size,Mean History Size,Median History Size');
|
||||||
|
|
||||||
|
dbUsers.find(
|
||||||
|
{
|
||||||
|
$and:
|
||||||
|
[
|
||||||
|
{'purchased.plan.planId': {$ne:null}},
|
||||||
|
{'purchased.plan.planId': {$ne:''}},
|
||||||
|
],
|
||||||
|
$or:
|
||||||
|
[
|
||||||
|
{'purchased.plan.dateTerminated': null},
|
||||||
|
{'purchased.plan.dateTerminated': ''},
|
||||||
|
{'purchased.plan.dateTerminated': {$gt:new Date()}},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fields: {_id: 1},
|
||||||
|
}
|
||||||
|
).each((user, {close, pause, resume}) => {
|
||||||
|
let historyLengths = [];
|
||||||
|
let habitCount = 0;
|
||||||
|
let dailyCount = 0;
|
||||||
|
|
||||||
|
pause();
|
||||||
|
return dbTasks.find(
|
||||||
|
{
|
||||||
|
userId: user._id,
|
||||||
|
$or:
|
||||||
|
[
|
||||||
|
{type: 'habit'},
|
||||||
|
{type: 'daily'},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fields: {
|
||||||
|
type: 1,
|
||||||
|
history: 1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
).each((task) => {
|
||||||
|
if (task.type === 'habit') {
|
||||||
|
habitCount++;
|
||||||
|
}
|
||||||
|
if (task.type === 'daily') {
|
||||||
|
dailyCount++;
|
||||||
|
}
|
||||||
|
if (task.history.length > 0) {
|
||||||
|
allHistoryLengths.push(task.history.length);
|
||||||
|
historyLengths.push(task.history.length);
|
||||||
|
}
|
||||||
|
}).then(() => {
|
||||||
|
const totalHistory = sum(historyLengths);
|
||||||
|
const maxHistory = historyLengths.length > 0 ? max(historyLengths) : 0;
|
||||||
|
const meanHistory = historyLengths.length > 0 ? round(mean(historyLengths)) : 0;
|
||||||
|
const medianHistory = historyLengths.length > 0 ? median(historyLengths) : 0;
|
||||||
|
console.info(`${user._id},${dailyCount},${habitCount},${totalHistory},${maxHistory},${meanHistory},${medianHistory}`);
|
||||||
|
resume();
|
||||||
|
});
|
||||||
|
}).then(() => {
|
||||||
|
console.info(`Total Subscriber History Entries: ${sum(allHistoryLengths)}`);
|
||||||
|
console.info(`Largest History Size: ${max(allHistoryLengths)}`);
|
||||||
|
console.info(`Mean History Size: ${round(mean(allHistoryLengths))}`);
|
||||||
|
console.info(`Median History Size: ${median(allHistoryLengths)}`);
|
||||||
|
return process.exit(0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function median(values) { // https://gist.github.com/caseyjustus/1166258
|
||||||
|
values.sort( function(a,b) {return a - b;} );
|
||||||
|
|
||||||
|
var half = Math.floor(values.length/2);
|
||||||
|
|
||||||
|
if (values.length % 2) {
|
||||||
|
return values[half];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return (values[half-1] + values[half]) / 2.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = usersReport;
|
||||||
48
database_reports/20181001_backtoschool_challenge.js
Normal file
48
database_reports/20181001_backtoschool_challenge.js
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import monk from 'monk';
|
||||||
|
import nconf from 'nconf';
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Output data on users who completed all the To-Do tasks in the 2018 Back-to-School Challenge.
|
||||||
|
* User ID,Profile Name
|
||||||
|
*/
|
||||||
|
const CONNECTION_STRING = nconf.get('MIGRATION_CONNECT_STRING');
|
||||||
|
const CHALLENGE_ID = '0acb1d56-1660-41a4-af80-9259f080b62b';
|
||||||
|
|
||||||
|
let dbUsers = monk(CONNECTION_STRING).get('users', { castIds: false });
|
||||||
|
let dbTasks = monk(CONNECTION_STRING).get('tasks', { castIds: false });
|
||||||
|
|
||||||
|
function usersReport() {
|
||||||
|
console.info('User ID,Profile Name');
|
||||||
|
let userCount = 0;
|
||||||
|
|
||||||
|
dbUsers.find(
|
||||||
|
{challenges: CHALLENGE_ID},
|
||||||
|
{fields:
|
||||||
|
{_id: 1, 'profile.name': 1}
|
||||||
|
},
|
||||||
|
).each((user, {close, pause, resume}) => {
|
||||||
|
pause();
|
||||||
|
userCount++;
|
||||||
|
let completedTodos = 0;
|
||||||
|
return dbTasks.find(
|
||||||
|
{
|
||||||
|
userId: user._id,
|
||||||
|
'challenge.id': CHALLENGE_ID,
|
||||||
|
type: 'todo',
|
||||||
|
},
|
||||||
|
{fields: {completed: 1}}
|
||||||
|
).each((task) => {
|
||||||
|
if (task.completed) completedTodos++;
|
||||||
|
}).then(() => {
|
||||||
|
if (completedTodos >= 7) {
|
||||||
|
console.info(`${user._id},${user.profile.name}`);
|
||||||
|
}
|
||||||
|
resume();
|
||||||
|
});
|
||||||
|
}).then(() => {
|
||||||
|
console.info(`${userCount} users reviewed`);
|
||||||
|
return process.exit(0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = usersReport;
|
||||||
@@ -2,9 +2,13 @@ version: "3"
|
|||||||
services:
|
services:
|
||||||
|
|
||||||
client:
|
client:
|
||||||
|
environment:
|
||||||
|
- NODE_ENV=development
|
||||||
volumes:
|
volumes:
|
||||||
- '.:/usr/src/habitrpg'
|
- '.:/usr/src/habitrpg'
|
||||||
|
|
||||||
server:
|
server:
|
||||||
|
environment:
|
||||||
|
- NODE_ENV=development
|
||||||
volumes:
|
volumes:
|
||||||
- '.:/usr/src/habitrpg'
|
- '.:/usr/src/habitrpg'
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ services:
|
|||||||
- mongo
|
- mongo
|
||||||
|
|
||||||
mongo:
|
mongo:
|
||||||
image: mongo
|
image: mongo:3.4
|
||||||
ports:
|
ports:
|
||||||
- "27017:27017"
|
- "27017:27017"
|
||||||
networks:
|
networks:
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ let improveRepl = (context) => {
|
|||||||
const mongooseOptions = !isProd ? {} : {
|
const mongooseOptions = !isProd ? {} : {
|
||||||
keepAlive: 1,
|
keepAlive: 1,
|
||||||
connectTimeoutMS: 30000,
|
connectTimeoutMS: 30000,
|
||||||
useMongoClient: true,
|
|
||||||
};
|
};
|
||||||
mongoose.connect(
|
mongoose.connect(
|
||||||
nconf.get('NODE_DB_URI'),
|
nconf.get('NODE_DB_URI'),
|
||||||
|
|||||||
@@ -165,9 +165,9 @@ gulp.task('test:content:safe', gulp.series('test:prepare:build', (cb) => {
|
|||||||
pipe(runner);
|
pipe(runner);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
gulp.task('test:api-v3:unit', (done) => {
|
gulp.task('test:api: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('istanbul cover --dir coverage/api-unit node_modules/mocha/bin/_mocha -- test/api/unit --recursive --require ./test/helpers/start-server'),
|
||||||
(err) => {
|
(err) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
@@ -179,13 +179,13 @@ gulp.task('test:api-v3:unit', (done) => {
|
|||||||
pipe(runner);
|
pipe(runner);
|
||||||
});
|
});
|
||||||
|
|
||||||
gulp.task('test:api-v3:unit:watch', () => {
|
gulp.task('test:api:unit:watch', () => {
|
||||||
return gulp.watch(['website/server/libs/*', 'test/api/v3/unit/**/*', 'website/server/controllers/**/*'], gulp.series('test:api-v3:unit', done => done()));
|
return gulp.watch(['website/server/libs/*', 'test/api/unit/**/*', 'website/server/controllers/**/*'], gulp.series('test:api:unit', done => done()));
|
||||||
});
|
});
|
||||||
|
|
||||||
gulp.task('test:api-v3:integration', (done) => {
|
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('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) => {
|
(err) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
@@ -215,17 +215,43 @@ gulp.task('test:api-v3:integration:separate-server', (done) => {
|
|||||||
pipe(runner);
|
pipe(runner);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
gulp.task('test:api-v4:integration', (done) => {
|
||||||
|
let runner = exec(
|
||||||
|
testBin('istanbul cover --dir coverage/api-v4-integration --report lcovonly node_modules/mocha/bin/_mocha -- test/api/v4 --recursive --require ./test/helpers/start-server'),
|
||||||
|
{maxBuffer: 500 * 1024},
|
||||||
|
(err) => {
|
||||||
|
if (err) {
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
pipe(runner);
|
||||||
|
});
|
||||||
|
|
||||||
|
gulp.task('test:api-v4:integration:separate-server', (done) => {
|
||||||
|
let runner = exec(
|
||||||
|
testBin('mocha test/api/v4 --recursive --require ./test/helpers/start-server', 'LOAD_SERVER=0'),
|
||||||
|
{maxBuffer: 500 * 1024},
|
||||||
|
(err) => done(err)
|
||||||
|
);
|
||||||
|
|
||||||
|
pipe(runner);
|
||||||
|
});
|
||||||
|
|
||||||
gulp.task('test', gulp.series(
|
gulp.task('test', gulp.series(
|
||||||
'test:sanity',
|
'test:sanity',
|
||||||
'test:content',
|
'test:content',
|
||||||
'test:common',
|
'test:common',
|
||||||
'test:api-v3:unit',
|
'test:api:unit',
|
||||||
'test:api-v3:integration',
|
'test:api-v3:integration',
|
||||||
|
'test:api-v4:integration',
|
||||||
done => done()
|
done => done()
|
||||||
));
|
));
|
||||||
|
|
||||||
gulp.task('test:api-v3', gulp.series(
|
gulp.task('test:api-v3', gulp.series(
|
||||||
'test:api-v3:unit',
|
'test:api:unit',
|
||||||
'test:api-v3:integration',
|
'test:api-v3:integration',
|
||||||
done => done()
|
done => done()
|
||||||
));
|
));
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { exec } from 'child_process';
|
|||||||
import psTree from 'ps-tree';
|
import psTree from 'ps-tree';
|
||||||
import nconf from 'nconf';
|
import nconf from 'nconf';
|
||||||
import net from 'net';
|
import net from 'net';
|
||||||
import Bluebird from 'bluebird';
|
|
||||||
import { post } from 'superagent';
|
import { post } from 'superagent';
|
||||||
import { sync as glob } from 'glob';
|
import { sync as glob } from 'glob';
|
||||||
import Mocha from 'mocha';
|
import Mocha from 'mocha';
|
||||||
@@ -45,7 +44,7 @@ export function kill (proc) {
|
|||||||
* before failing.
|
* before failing.
|
||||||
*/
|
*/
|
||||||
export function awaitPort (port, max = 60) {
|
export function awaitPort (port, max = 60) {
|
||||||
return new Bluebird((rej, res) => {
|
return new Promise((rej, res) => {
|
||||||
let socket;
|
let socket;
|
||||||
let timeout;
|
let timeout;
|
||||||
let interval;
|
let interval;
|
||||||
|
|||||||
@@ -16,7 +16,6 @@
|
|||||||
const authorName = 'Blade';
|
const authorName = 'Blade';
|
||||||
const authorUuid = '75f270e8-c5db-4722-a5e6-a83f1b23f76b';
|
const authorUuid = '75f270e8-c5db-4722-a5e6-a83f1b23f76b';
|
||||||
|
|
||||||
global.Promise = require('bluebird');
|
|
||||||
const MongoClient = require('mongodb').MongoClient;
|
const MongoClient = require('mongodb').MongoClient;
|
||||||
const TaskQueue = require('cwait').TaskQueue;
|
const TaskQueue = require('cwait').TaskQueue;
|
||||||
const logger = require('./utils/logger');
|
const logger = require('./utils/logger');
|
||||||
|
|||||||
@@ -11,7 +11,6 @@
|
|||||||
* pm'ed each user asking if they would like their tasks reset to the previous day
|
* pm'ed each user asking if they would like their tasks reset to the previous day
|
||||||
***************************************/
|
***************************************/
|
||||||
|
|
||||||
global.Promise = require('bluebird');
|
|
||||||
const MongoClient = require('mongodb').MongoClient;
|
const MongoClient = require('mongodb').MongoClient;
|
||||||
const TaskQueue = require('cwait').TaskQueue;
|
const TaskQueue = require('cwait').TaskQueue;
|
||||||
const logger = require('./utils/logger');
|
const logger = require('./utils/logger');
|
||||||
|
|||||||
@@ -12,7 +12,6 @@
|
|||||||
* from an object to a number, hence this migration.
|
* from an object to a number, hence this migration.
|
||||||
***************************************/
|
***************************************/
|
||||||
|
|
||||||
global.Promise = require('bluebird');
|
|
||||||
const TaskQueue = require('cwait').TaskQueue;
|
const TaskQueue = require('cwait').TaskQueue;
|
||||||
const logger = require('./utils/logger');
|
const logger = require('./utils/logger');
|
||||||
const Timer = require('./utils/timer');
|
const Timer = require('./utils/timer');
|
||||||
|
|||||||
@@ -9,7 +9,6 @@
|
|||||||
* and transfers a group's progress to it
|
* and transfers a group's progress to it
|
||||||
***************************************/
|
***************************************/
|
||||||
|
|
||||||
global.Promise = require('bluebird');
|
|
||||||
const TaskQueue = require('cwait').TaskQueue;
|
const TaskQueue = require('cwait').TaskQueue;
|
||||||
const logger = require('./utils/logger');
|
const logger = require('./utils/logger');
|
||||||
const Timer = require('./utils/timer');
|
const Timer = require('./utils/timer');
|
||||||
|
|||||||
@@ -12,7 +12,6 @@
|
|||||||
* <userid>@example.com
|
* <userid>@example.com
|
||||||
***************************************/
|
***************************************/
|
||||||
|
|
||||||
global.Promise = require('bluebird');
|
|
||||||
const TaskQueue = require('cwait').TaskQueue;
|
const TaskQueue = require('cwait').TaskQueue;
|
||||||
const logger = require('./utils/logger');
|
const logger = require('./utils/logger');
|
||||||
const Timer = require('./utils/timer');
|
const Timer = require('./utils/timer');
|
||||||
|
|||||||
@@ -9,7 +9,6 @@
|
|||||||
* they support a type and options and label
|
* they support a type and options and label
|
||||||
* ***************************************/
|
* ***************************************/
|
||||||
|
|
||||||
global.Promise = require('bluebird');
|
|
||||||
const TaskQueue = require('cwait').TaskQueue;
|
const TaskQueue = require('cwait').TaskQueue;
|
||||||
const logger = require('./utils/logger');
|
const logger = require('./utils/logger');
|
||||||
const Timer = require('./utils/timer');
|
const Timer = require('./utils/timer');
|
||||||
|
|||||||
@@ -12,7 +12,6 @@
|
|||||||
* message into the chat for affected parties.
|
* message into the chat for affected parties.
|
||||||
***************************************/
|
***************************************/
|
||||||
|
|
||||||
global.Promise = require('bluebird');
|
|
||||||
const uuid = require('uuid');
|
const uuid = require('uuid');
|
||||||
const TaskQueue = require('cwait').TaskQueue;
|
const TaskQueue = require('cwait').TaskQueue;
|
||||||
const logger = require('./utils/logger');
|
const logger = require('./utils/logger');
|
||||||
|
|||||||
88
migrations/archive/2017/20171211_sanitize_emails.js
Normal file
88
migrations/archive/2017/20171211_sanitize_emails.js
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
var migrationName = '20171211_sanitize_emails.js';
|
||||||
|
var authorName = 'Julius'; // in case script author needs to know when their ...
|
||||||
|
var authorUuid = 'dd16c270-1d6d-44bd-b4f9-737342e79be6'; //... own data is done
|
||||||
|
|
||||||
|
/*
|
||||||
|
User creation saves email as lowercase, but updating an email did not.
|
||||||
|
Run this script to ensure all lowercased emails in db AFTER fix for updating emails is implemented.
|
||||||
|
This will fix inconsistent querying for an email when attempting to password reset.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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) {
|
||||||
|
var query = {
|
||||||
|
'auth.local.email': /[A-Z]/
|
||||||
|
};
|
||||||
|
|
||||||
|
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)
|
||||||
|
'auth.local.email'
|
||||||
|
],
|
||||||
|
})
|
||||||
|
.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 = {
|
||||||
|
'auth.local.email': user.auth.local.email.toLowerCase()
|
||||||
|
};
|
||||||
|
|
||||||
|
dbUsers.update({_id: user._id}, {$set: set});
|
||||||
|
|
||||||
|
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;
|
||||||
99
migrations/archive/2018/20180724_summer_splash_orcas.js
Normal file
99
migrations/archive/2018/20180724_summer_splash_orcas.js
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
let migrationName = '20180724_summer-splash-orcas.js'; // Update per month
|
||||||
|
let authorName = 'Sabe'; // in case script author needs to know when their ...
|
||||||
|
let authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; // ... own data is done
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Award ladder items to participants in this year's Summer Splash festivities
|
||||||
|
*/
|
||||||
|
|
||||||
|
import monk from 'monk';
|
||||||
|
import nconf from 'nconf';
|
||||||
|
const CONNECTION_STRING = nconf.get('MIGRATION_CONNECT_STRING');
|
||||||
|
let dbUsers = monk(CONNECTION_STRING).get('users', { castIds: false });
|
||||||
|
|
||||||
|
function processUsers (lastId) {
|
||||||
|
// specify a query to limit the affected users (empty for all users):
|
||||||
|
let query = {
|
||||||
|
migration: {$ne: migrationName},
|
||||||
|
'auth.timestamps.loggedin': {$gt: new Date('2018-07-01')}, // rerun without date restriction after initial run
|
||||||
|
};
|
||||||
|
|
||||||
|
if (lastId) {
|
||||||
|
query._id = {
|
||||||
|
$gt: lastId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
dbUsers.find(query, {
|
||||||
|
sort: {_id: 1},
|
||||||
|
limit: 250,
|
||||||
|
fields: [
|
||||||
|
'items.mounts',
|
||||||
|
], // specify fields we are interested in to limit retrieved data (empty if we're not reading data):
|
||||||
|
})
|
||||||
|
.then(updateUsers)
|
||||||
|
.catch((err) => {
|
||||||
|
console.log(err);
|
||||||
|
return exiting(1, `ERROR! ${ err}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let progressCount = 1000;
|
||||||
|
let count = 0;
|
||||||
|
|
||||||
|
function updateUsers (users) {
|
||||||
|
if (!users || users.length === 0) {
|
||||||
|
console.warn('All appropriate users found and modified.');
|
||||||
|
displayData();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let userPromises = users.map(updateUser);
|
||||||
|
let lastUser = users[users.length - 1];
|
||||||
|
|
||||||
|
return Promise.all(userPromises)
|
||||||
|
.then(() => {
|
||||||
|
processUsers(lastUser._id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateUser (user) {
|
||||||
|
count++;
|
||||||
|
|
||||||
|
let set = {};
|
||||||
|
|
||||||
|
if (user && user.items && user.items.pets && typeof user.items.pets['Orca-Base'] !== 'undefined') {
|
||||||
|
set = {migration: migrationName};
|
||||||
|
} else if (user && user.items && user.items.mounts && typeof user.items.mounts['Orca-Base'] !== 'undefined') {
|
||||||
|
set = {migration: migrationName, 'items.pets.Orca-Base': 5};
|
||||||
|
} else {
|
||||||
|
set = {migration: migrationName, 'items.mounts.Orca-Base': true};
|
||||||
|
}
|
||||||
|
|
||||||
|
dbUsers.update({_id: user._id}, {$set: set});
|
||||||
|
|
||||||
|
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;
|
||||||
123
migrations/archive/2018/20180731_naming_day.js
Normal file
123
migrations/archive/2018/20180731_naming_day.js
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
let migrationName = '20180731_naming-day.js'; // Update when running in future years
|
||||||
|
let authorName = 'Sabe'; // in case script author needs to know when their ...
|
||||||
|
let authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; // ... own data is done
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Award Naming Day ladder items to participants in this month's Naming Day festivities
|
||||||
|
*/
|
||||||
|
|
||||||
|
import monk from 'monk';
|
||||||
|
import nconf from 'nconf';
|
||||||
|
const CONNECTION_STRING = nconf.get('MIGRATION_CONNECT_STRING'); // FOR TEST DATABASE
|
||||||
|
let dbUsers = monk(CONNECTION_STRING).get('users', { castIds: false });
|
||||||
|
|
||||||
|
function processUsers (lastId) {
|
||||||
|
// specify a query to limit the affected users (empty for all users):
|
||||||
|
let query = {
|
||||||
|
migration: {$ne: migrationName},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (lastId) {
|
||||||
|
query._id = {
|
||||||
|
$gt: lastId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
dbUsers.find(query, {
|
||||||
|
sort: {_id: 1},
|
||||||
|
limit: 250,
|
||||||
|
fields: [
|
||||||
|
'items.gear.owned',
|
||||||
|
'items.mounts',
|
||||||
|
'items.pets',
|
||||||
|
], // specify fields we are interested in to limit retrieved data (empty if we're not reading data):
|
||||||
|
})
|
||||||
|
.then(updateUsers)
|
||||||
|
.catch((err) => {
|
||||||
|
console.log(err);
|
||||||
|
return exiting(1, `ERROR! ${ err}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let progressCount = 1000;
|
||||||
|
let count = 0;
|
||||||
|
|
||||||
|
function updateUsers (users) {
|
||||||
|
if (!users || users.length === 0) {
|
||||||
|
console.warn('All appropriate users found and modified.');
|
||||||
|
displayData();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let userPromises = users.map(updateUser);
|
||||||
|
let lastUser = users[users.length - 1];
|
||||||
|
|
||||||
|
return Promise.all(userPromises)
|
||||||
|
.then(() => {
|
||||||
|
processUsers(lastUser._id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateUser (user) {
|
||||||
|
count++;
|
||||||
|
|
||||||
|
let set = {};
|
||||||
|
let push;
|
||||||
|
|
||||||
|
const inc = {
|
||||||
|
'items.food.Cake_Base': 1,
|
||||||
|
'items.food.Cake_CottonCandyBlue': 1,
|
||||||
|
'items.food.Cake_CottonCandyPink': 1,
|
||||||
|
'items.food.Cake_Desert': 1,
|
||||||
|
'items.food.Cake_Golden': 1,
|
||||||
|
'items.food.Cake_Red': 1,
|
||||||
|
'items.food.Cake_Shade': 1,
|
||||||
|
'items.food.Cake_Skeleton': 1,
|
||||||
|
'items.food.Cake_White': 1,
|
||||||
|
'items.food.Cake_Zombie': 1,
|
||||||
|
'achievements.habiticaDays': 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (user && user.items && user.items.gear && user.items.gear.owned && typeof user.items.gear.owned.head_special_namingDay2017 !== 'undefined') {
|
||||||
|
set = {migration: migrationName, 'items.gear.owned.body_special_namingDay2018': false};
|
||||||
|
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.body_special_namingDay2018', _id: monk.id()}};
|
||||||
|
} else if (user && user.items && user.items.pets && typeof user.items.pets['Gryphon-RoyalPurple'] !== 'undefined') {
|
||||||
|
set = {migration: migrationName, 'items.gear.owned.head_special_namingDay2017': false};
|
||||||
|
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.head_special_namingDay2017', _id: monk.id()}};
|
||||||
|
} else if (user && user.items && user.items.mounts && typeof user.items.mounts['Gryphon-RoyalPurple'] !== 'undefined') {
|
||||||
|
set = {migration: migrationName, 'items.pets.Gryphon-RoyalPurple': 5};
|
||||||
|
} else {
|
||||||
|
set = {migration: migrationName, 'items.mounts.Gryphon-RoyalPurple': true};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (push) {
|
||||||
|
dbUsers.update({_id: user._id}, {$set: set, $push: push, $inc: inc});
|
||||||
|
} else {
|
||||||
|
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;
|
||||||
147
migrations/archive/2018/20180811_inboxOutsideUser.js
Normal file
147
migrations/archive/2018/20180811_inboxOutsideUser.js
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
const migrationName = '20180811_inboxOutsideUser.js';
|
||||||
|
const authorName = 'paglias'; // in case script author needs to know when their ...
|
||||||
|
const authorUuid = 'ed4c688c-6652-4a92-9d03-a5a79844174a'; // ... own data is done
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Move inbox messages from the user model to their own collection
|
||||||
|
*/
|
||||||
|
|
||||||
|
const monk = require('monk');
|
||||||
|
const nconf = require('nconf');
|
||||||
|
const uuid = require('uuid').v4;
|
||||||
|
|
||||||
|
const Inbox = require('../website/server/models/message').inboxModel;
|
||||||
|
const connectionString = nconf.get('MIGRATION_CONNECT_STRING'); // FOR TEST DATABASE
|
||||||
|
const dbInboxes = monk(connectionString).get('inboxes', { castIds: false });
|
||||||
|
const dbUsers = monk(connectionString).get('users', { castIds: false });
|
||||||
|
|
||||||
|
function processUsers (lastId) {
|
||||||
|
let query = {
|
||||||
|
migration: {$ne: migrationName},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (lastId) {
|
||||||
|
query._id = {
|
||||||
|
$gt: lastId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
dbUsers.find(query, {
|
||||||
|
sort: {_id: 1},
|
||||||
|
limit: 1000,
|
||||||
|
fields: ['_id', 'inbox'],
|
||||||
|
})
|
||||||
|
.then(updateUsers)
|
||||||
|
.catch((err) => {
|
||||||
|
console.log(err);
|
||||||
|
return exiting(1, `ERROR! ${ err}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let progressCount = 1000;
|
||||||
|
let count = 0;
|
||||||
|
let msgCount = 0;
|
||||||
|
|
||||||
|
function updateUsers (users) {
|
||||||
|
if (!users || users.length === 0) {
|
||||||
|
console.warn('All appropriate users and their tasks found and modified.');
|
||||||
|
displayData();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let usersPromises = users.map(updateUser);
|
||||||
|
let lastUser = users[users.length - 1];
|
||||||
|
|
||||||
|
return Promise.all(usersPromises)
|
||||||
|
.then(() => {
|
||||||
|
return processUsers(lastUser._id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateUser (user) {
|
||||||
|
count++;
|
||||||
|
|
||||||
|
if (count % progressCount === 0) console.warn(`${count } ${ user._id}`);
|
||||||
|
if (msgCount % progressCount === 0) console.warn(`${msgCount } messages processed`);
|
||||||
|
if (user._id === authorUuid) console.warn(`${authorName } being processed`);
|
||||||
|
|
||||||
|
const oldInboxMessages = user.inbox.messages || {};
|
||||||
|
const oldInboxMessagesIds = Object.keys(oldInboxMessages);
|
||||||
|
|
||||||
|
msgCount += oldInboxMessagesIds.length;
|
||||||
|
|
||||||
|
const newInboxMessages = oldInboxMessagesIds.map(msgId => {
|
||||||
|
const msg = oldInboxMessages[msgId];
|
||||||
|
if (!msg || (!msg.id && !msg._id)) { // eslint-disable-line no-extra-parens
|
||||||
|
console.log('missing message or message _id and id', msg);
|
||||||
|
throw new Error('error!');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msg.id && !msg._id) msg._id = msg.id;
|
||||||
|
if (msg._id && !msg.id) msg.id = msg._id;
|
||||||
|
|
||||||
|
const newMsg = new Inbox(msg);
|
||||||
|
newMsg.ownerId = user._id;
|
||||||
|
return newMsg.toJSON();
|
||||||
|
});
|
||||||
|
|
||||||
|
const promises = newInboxMessages.map(newMsg => {
|
||||||
|
return (async function fn () {
|
||||||
|
const existing = await dbInboxes.find({_id: newMsg._id});
|
||||||
|
|
||||||
|
if (existing.length > 0) {
|
||||||
|
if (
|
||||||
|
existing[0].ownerId === newMsg.ownerId &&
|
||||||
|
existing[0].text === newMsg.text &&
|
||||||
|
existing[0].uuid === newMsg.uuid &&
|
||||||
|
existing[0].sent === newMsg.sent
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
newMsg.id = newMsg._id = uuid();
|
||||||
|
}
|
||||||
|
|
||||||
|
return newMsg;
|
||||||
|
})();
|
||||||
|
});
|
||||||
|
|
||||||
|
return Promise.all(promises)
|
||||||
|
.then((filteredNewMsg) => {
|
||||||
|
filteredNewMsg = filteredNewMsg.filter(m => Boolean(m && m.id && m._id && m.id == m._id));
|
||||||
|
return dbInboxes.insert(filteredNewMsg);
|
||||||
|
}).then(() => {
|
||||||
|
return dbUsers.update({_id: user._id}, {
|
||||||
|
$set: {
|
||||||
|
migration: migrationName,
|
||||||
|
'inbox.messages': {},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}).catch((err) => {
|
||||||
|
console.log(err);
|
||||||
|
return exiting(1, `ERROR! ${ err}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function displayData () {
|
||||||
|
console.warn(`\n${ count } users processed\n`);
|
||||||
|
console.warn(`\n${ msgCount } messages 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;
|
||||||
99
migrations/archive/2018/20181001_generate_usernames.js
Normal file
99
migrations/archive/2018/20181001_generate_usernames.js
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
let authorName = 'Sabe'; // in case script author needs to know when their ...
|
||||||
|
let authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; // ... own data is done
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Generate usernames for users who lack them
|
||||||
|
*/
|
||||||
|
|
||||||
|
import monk from 'monk';
|
||||||
|
import nconf from 'nconf';
|
||||||
|
import { generateUsername } from '../../website/server/libs/auth/utils';
|
||||||
|
const CONNECTION_STRING = nconf.get('MIGRATION_CONNECT_STRING'); // FOR TEST DATABASE
|
||||||
|
let dbUsers = monk(CONNECTION_STRING).get('users', { castIds: false });
|
||||||
|
|
||||||
|
function processUsers (lastId) {
|
||||||
|
// specify a query to limit the affected users (empty for all users):
|
||||||
|
let query = {
|
||||||
|
'auth.local.username': {$exists: false},
|
||||||
|
'auth.timestamps.loggedin': {$gt: new Date('2018-04-01')}, // Initial coverage for users active within last 6 months
|
||||||
|
};
|
||||||
|
|
||||||
|
if (lastId) {
|
||||||
|
query._id = {
|
||||||
|
$gt: lastId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
dbUsers.find(query, {
|
||||||
|
sort: {_id: 1},
|
||||||
|
limit: 250,
|
||||||
|
fields: [
|
||||||
|
'auth',
|
||||||
|
], // specify fields we are interested in to limit retrieved data (empty if we're not reading data):
|
||||||
|
})
|
||||||
|
.then(updateUsers)
|
||||||
|
.catch((err) => {
|
||||||
|
console.log(err);
|
||||||
|
return exiting(1, `ERROR! ${ err}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let progressCount = 1000;
|
||||||
|
let count = 0;
|
||||||
|
|
||||||
|
function updateUsers (users) {
|
||||||
|
if (!users || users.length === 0) {
|
||||||
|
console.warn('All appropriate users found and modified.');
|
||||||
|
displayData();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let userPromises = users.map(updateUser);
|
||||||
|
let lastUser = users[users.length - 1];
|
||||||
|
|
||||||
|
return Promise.all(userPromises)
|
||||||
|
.then(() => {
|
||||||
|
processUsers(lastUser._id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateUser (user) {
|
||||||
|
count++;
|
||||||
|
|
||||||
|
if (!user.auth.local.username) {
|
||||||
|
const newName = generateUsername();
|
||||||
|
dbUsers.update(
|
||||||
|
{_id: user._id},
|
||||||
|
{$set:
|
||||||
|
{
|
||||||
|
'auth.local.username': newName,
|
||||||
|
'auth.local.lowerCaseUsername': newName,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
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;
|
||||||
107
migrations/archive/2018/20181002_username_email.js
Normal file
107
migrations/archive/2018/20181002_username_email.js
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
const MIGRATION_NAME = '20181003_username_email.js';
|
||||||
|
let authorName = 'Sabe'; // in case script author needs to know when their ...
|
||||||
|
let authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; // ... own data is done
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Send emails to eligible users announcing upcoming username changes
|
||||||
|
*/
|
||||||
|
|
||||||
|
import monk from 'monk';
|
||||||
|
import nconf from 'nconf';
|
||||||
|
import { sendTxn } from '../../website/server/libs/email';
|
||||||
|
const CONNECTION_STRING = nconf.get('MIGRATION_CONNECT_STRING');
|
||||||
|
let dbUsers = monk(CONNECTION_STRING).get('users', { castIds: false });
|
||||||
|
|
||||||
|
function processUsers (lastId) {
|
||||||
|
// specify a query to limit the affected users (empty for all users):
|
||||||
|
let query = {
|
||||||
|
migration: {$ne: MIGRATION_NAME},
|
||||||
|
'auth.timestamps.loggedin': {$gt: new Date('2018-04-01')},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (lastId) {
|
||||||
|
query._id = {
|
||||||
|
$gt: lastId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
dbUsers.find(query, {
|
||||||
|
sort: {_id: 1},
|
||||||
|
limit: 100,
|
||||||
|
fields: [
|
||||||
|
'_id',
|
||||||
|
'auth',
|
||||||
|
'preferences',
|
||||||
|
'profile',
|
||||||
|
], // specify fields we are interested in to limit retrieved data (empty if we're not reading data):
|
||||||
|
})
|
||||||
|
.then(updateUsers)
|
||||||
|
.catch((err) => {
|
||||||
|
console.log(err);
|
||||||
|
return exiting(1, `ERROR! ${ err}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let progressCount = 1000;
|
||||||
|
let count = 0;
|
||||||
|
|
||||||
|
function updateUsers (users) {
|
||||||
|
if (!users || users.length === 0) {
|
||||||
|
console.warn('All appropriate users found and modified.');
|
||||||
|
displayData();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let userPromises = users.map(updateUser);
|
||||||
|
let lastUser = users[users.length - 1];
|
||||||
|
|
||||||
|
return Promise.all(userPromises)
|
||||||
|
.then(() => delay(7000))
|
||||||
|
.then(() => {
|
||||||
|
processUsers(lastUser._id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateUser (user) {
|
||||||
|
count++;
|
||||||
|
|
||||||
|
dbUsers.update({_id: user._id}, {$set: {migration: MIGRATION_NAME}});
|
||||||
|
|
||||||
|
sendTxn(
|
||||||
|
user,
|
||||||
|
'username-change',
|
||||||
|
[{name: 'UNSUB_EMAIL_TYPE_URL', content: '/user/settings/notifications?unsubFrom=importantAnnouncements'},
|
||||||
|
{name: 'LOGIN_NAME', content: user.auth.local.username}]
|
||||||
|
);
|
||||||
|
|
||||||
|
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 delay (t, v) {
|
||||||
|
return new Promise(function batchPause (resolve) {
|
||||||
|
setTimeout(resolve.bind(null, v), t);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
66
migrations/archive/2018/20181023_veteran_pet_ladder.js
Normal file
66
migrations/archive/2018/20181023_veteran_pet_ladder.js
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
/* eslint-disable no-console */
|
||||||
|
const MIGRATION_NAME = '20181023_veteran_pet_ladder';
|
||||||
|
import { model as User } from '../../website/server/models/user';
|
||||||
|
|
||||||
|
const progressCount = 1000;
|
||||||
|
let count = 0;
|
||||||
|
|
||||||
|
async function updateUser (user) {
|
||||||
|
count++;
|
||||||
|
|
||||||
|
const set = {};
|
||||||
|
|
||||||
|
set.migration = MIGRATION_NAME;
|
||||||
|
|
||||||
|
if (user.items.pets['Bear-Veteran']) {
|
||||||
|
set['items.pets.Fox-Veteran'] = 5;
|
||||||
|
} else 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}`);
|
||||||
|
|
||||||
|
return await User.update({_id: user._id}, {$set: set}).exec();
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = async function processUsers () {
|
||||||
|
let query = {
|
||||||
|
migration: {$ne: MIGRATION_NAME},
|
||||||
|
'flags.verifiedUsername': true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const fields = {
|
||||||
|
_id: 1,
|
||||||
|
items: 1,
|
||||||
|
migration: 1,
|
||||||
|
flags: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
while (true) { // eslint-disable-line no-constant-condition
|
||||||
|
const users = await User // eslint-disable-line no-await-in-loop
|
||||||
|
.find(query)
|
||||||
|
.limit(250)
|
||||||
|
.sort({_id: 1})
|
||||||
|
.select(fields)
|
||||||
|
.lean()
|
||||||
|
.exec();
|
||||||
|
|
||||||
|
if (users.length === 0) {
|
||||||
|
console.warn('All appropriate users found and modified.');
|
||||||
|
console.warn(`\n${count} users processed\n`);
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
query._id = {
|
||||||
|
$gt: users[users.length - 1],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
|
||||||
|
}
|
||||||
|
};
|
||||||
116
migrations/archive/2018/20181030_habitoween_ladder.js
Normal file
116
migrations/archive/2018/20181030_habitoween_ladder.js
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
/*
|
||||||
|
* Award Habitoween ladder items to participants in this month's Habitoween festivities
|
||||||
|
*/
|
||||||
|
|
||||||
|
import monk from 'monk';
|
||||||
|
import nconf from 'nconf';
|
||||||
|
const MIGRATION_NAME = '20181030_habitoween_ladder.js'; // Update when running in future years
|
||||||
|
const CONNECTION_STRING = nconf.get('MIGRATION_CONNECT_STRING');
|
||||||
|
const AUTHOR_NAME = 'Sabe'; // in case script author needs to know when their ...
|
||||||
|
const AUTHOR_UUID = '7f14ed62-5408-4e1b-be83-ada62d504931'; // ... own data is done
|
||||||
|
|
||||||
|
let dbUsers = monk(CONNECTION_STRING).get('users', { castIds: false });
|
||||||
|
|
||||||
|
function processUsers (lastId) {
|
||||||
|
// specify a query to limit the affected users (empty for all users):
|
||||||
|
let query = {
|
||||||
|
migration: {$ne: MIGRATION_NAME},
|
||||||
|
'auth.timestamps.loggedin': {$gt: new Date('2018-10-01')},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (lastId) {
|
||||||
|
query._id = {
|
||||||
|
$gt: lastId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
dbUsers.find(query, {
|
||||||
|
sort: {_id: 1},
|
||||||
|
limit: 250,
|
||||||
|
fields: [
|
||||||
|
'items.mounts',
|
||||||
|
'items.pets',
|
||||||
|
], // specify fields we are interested in to limit retrieved data (empty if we're not reading data):
|
||||||
|
})
|
||||||
|
.then(updateUsers)
|
||||||
|
.catch((err) => {
|
||||||
|
console.log(err);
|
||||||
|
return exiting(1, `ERROR! ${err}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const PROGRESS_COUNT = 1000;
|
||||||
|
let count = 0;
|
||||||
|
|
||||||
|
function updateUsers (users) {
|
||||||
|
if (!users || users.length === 0) {
|
||||||
|
console.warn('All appropriate users found and modified.');
|
||||||
|
displayData();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let userPromises = users.map(updateUser);
|
||||||
|
let lastUser = users[users.length - 1];
|
||||||
|
|
||||||
|
return Promise.all(userPromises)
|
||||||
|
.then(() => {
|
||||||
|
processUsers(lastUser._id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateUser (user) {
|
||||||
|
count++;
|
||||||
|
|
||||||
|
let set = {};
|
||||||
|
let 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.mounts['JackOLantern-Ghost']) {
|
||||||
|
set['items.pets.JackOLantern-Glow'] = 5;
|
||||||
|
} else if (user && user.items && user.items.pets && user.items.pets['JackOLantern-Ghost']) {
|
||||||
|
set['items.mounts.JackOLantern-Ghost'] = true;
|
||||||
|
} else if (user && user.items && user.items.mounts && user.items.mounts['JackOLantern-Base']) {
|
||||||
|
set['items.pets.JackOLantern-Ghost'] = 5;
|
||||||
|
} else if (user && user.items && user.items.pets && user.items.pets['JackOLantern-Base']) {
|
||||||
|
set['items.mounts.JackOLantern-Base'] = true;
|
||||||
|
} else {
|
||||||
|
set['items.pets.JackOLantern-Base'] = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
dbUsers.update({_id: user._id}, {$set: set, $inc: inc});
|
||||||
|
|
||||||
|
if (count % PROGRESS_COUNT === 0) console.warn(`${count} ${user._id}`);
|
||||||
|
if (user._id === AUTHOR_UUID) console.warn(`${AUTHOR_NAME} 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;
|
||||||
109
migrations/archive/2018/20181108_username_email.js
Normal file
109
migrations/archive/2018/20181108_username_email.js
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
const MIGRATION_NAME = '20181108_username_email.js';
|
||||||
|
const AUTHOR_NAME = 'Sabe'; // in case script author needs to know when their ...
|
||||||
|
const AUTHOR_UUID = '7f14ed62-5408-4e1b-be83-ada62d504931'; // ... own data is done
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Send emails to eligible users announcing upcoming username changes
|
||||||
|
*/
|
||||||
|
|
||||||
|
import monk from 'monk';
|
||||||
|
import nconf from 'nconf';
|
||||||
|
import { sendTxn } from '../../../website/server/libs/email';
|
||||||
|
const CONNECTION_STRING = nconf.get('MIGRATION_CONNECT_STRING');
|
||||||
|
const BASE_URL = nconf.get('BASE_URL');
|
||||||
|
let dbUsers = monk(CONNECTION_STRING).get('users', { castIds: false });
|
||||||
|
|
||||||
|
function processUsers (lastId) {
|
||||||
|
// specify a query to limit the affected users (empty for all users):
|
||||||
|
let query = {
|
||||||
|
migration: {$ne: MIGRATION_NAME},
|
||||||
|
'flags.verifiedUsername': {$ne: true},
|
||||||
|
'auth.timestamps.loggedin': {$gt: new Date('2018-10-25')},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (lastId) {
|
||||||
|
query._id = {
|
||||||
|
$gt: lastId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
dbUsers.find(query, {
|
||||||
|
sort: {_id: 1},
|
||||||
|
limit: 100,
|
||||||
|
fields: [
|
||||||
|
'_id',
|
||||||
|
'auth',
|
||||||
|
'preferences',
|
||||||
|
'profile',
|
||||||
|
], // specify fields we are interested in to limit retrieved data (empty if we're not reading data):
|
||||||
|
})
|
||||||
|
.then(updateUsers)
|
||||||
|
.catch((err) => {
|
||||||
|
console.log(err);
|
||||||
|
return exiting(1, `ERROR! ${ err}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let progressCount = 1000;
|
||||||
|
let count = 0;
|
||||||
|
|
||||||
|
function updateUsers (users) {
|
||||||
|
if (!users || users.length === 0) {
|
||||||
|
console.warn('All appropriate users found and modified.');
|
||||||
|
displayData();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let userPromises = users.map(updateUser);
|
||||||
|
let lastUser = users[users.length - 1];
|
||||||
|
|
||||||
|
return Promise.all(userPromises)
|
||||||
|
.then(() => delay(7000))
|
||||||
|
.then(() => {
|
||||||
|
processUsers(lastUser._id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateUser (user) {
|
||||||
|
count++;
|
||||||
|
|
||||||
|
dbUsers.update({_id: user._id}, {$set: {migration: MIGRATION_NAME}});
|
||||||
|
|
||||||
|
sendTxn(
|
||||||
|
user,
|
||||||
|
'username-change-follow-up',
|
||||||
|
[{name: 'LOGIN_NAME', content: user.auth.local.username},
|
||||||
|
{name: 'BASE_URL', content: BASE_URL}]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
|
||||||
|
if (user._id === AUTHOR_UUID) console.warn(`${AUTHOR_NAME} processed`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function displayData () {
|
||||||
|
console.warn(`\n${count} users processed\n`);
|
||||||
|
return exiting(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function delay (t, v) {
|
||||||
|
return new Promise(function batchPause (resolve) {
|
||||||
|
setTimeout(resolve.bind(null, v), t);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
109
migrations/archive/2018/20181122_turkey_day.js
Normal file
109
migrations/archive/2018/20181122_turkey_day.js
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
/* eslint-disable no-console */
|
||||||
|
const MIGRATION_NAME = '20181122_turkey_day';
|
||||||
|
import mongoose from 'mongoose';
|
||||||
|
import { model as User } from '../../website/server/models/user';
|
||||||
|
|
||||||
|
const progressCount = 1000;
|
||||||
|
let count = 0;
|
||||||
|
|
||||||
|
async function updateUser (user) {
|
||||||
|
count++;
|
||||||
|
|
||||||
|
const set = {};
|
||||||
|
let push;
|
||||||
|
|
||||||
|
set.migration = MIGRATION_NAME;
|
||||||
|
|
||||||
|
if (typeof user.items.gear.owned.armor_special_turkeyArmorBase !== 'undefined') {
|
||||||
|
set['items.gear.owned.head_special_turkeyHelmGilded'] = false;
|
||||||
|
set['items.gear.owned.armor_special_turkeyArmorGilded'] = false;
|
||||||
|
set['items.gear.owned.back_special_turkeyTailGilded'] = false;
|
||||||
|
push = [
|
||||||
|
{
|
||||||
|
type: 'marketGear',
|
||||||
|
path: 'gear.flat.head_special_turkeyHelmGilded',
|
||||||
|
_id: new mongoose.Types.ObjectId(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'marketGear',
|
||||||
|
path: 'gear.flat.armor_special_turkeyArmorGilded',
|
||||||
|
_id: new mongoose.Types.ObjectId(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'marketGear',
|
||||||
|
path: 'gear.flat.back_special_turkeyTailGilded',
|
||||||
|
_id: new mongoose.Types.ObjectId(),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
} else if (user.items && user.items.mounts && user.items.mounts['Turkey-Gilded']) {
|
||||||
|
set['items.gear.owned.head_special_turkeyHelmBase'] = false;
|
||||||
|
set['items.gear.owned.armor_special_turkeyArmorBase'] = false;
|
||||||
|
set['items.gear.owned.back_special_turkeyTailBase'] = false;
|
||||||
|
push = [
|
||||||
|
{
|
||||||
|
type: 'marketGear',
|
||||||
|
path: 'gear.flat.head_special_turkeyHelmBase',
|
||||||
|
_id: new mongoose.Types.ObjectId(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'marketGear',
|
||||||
|
path: 'gear.flat.armor_special_turkeyArmorBase',
|
||||||
|
_id: new mongoose.Types.ObjectId(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'marketGear',
|
||||||
|
path: 'gear.flat.back_special_turkeyTailBase',
|
||||||
|
_id: new mongoose.Types.ObjectId(),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
} else if (user.items && user.items.pets && user.items.pets['Turkey-Gilded']) {
|
||||||
|
set['items.mounts.Turkey-Gilded'] = true;
|
||||||
|
} else if (user.items && user.items.mounts && user.items.mounts['Turkey-Base']) {
|
||||||
|
set['items.pets.Turkey-Gilded'] = 5;
|
||||||
|
} else if (user.items && user.items.pets && user.items.pets['Turkey-Base']) {
|
||||||
|
set['items.mounts.Turkey-Base'] = true;
|
||||||
|
} else {
|
||||||
|
set['items.pets.Turkey-Base'] = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
|
||||||
|
|
||||||
|
if (push) {
|
||||||
|
return await User.update({_id: user._id}, {$set: set, $push: {pinnedItems: {$each: push}}}).exec();
|
||||||
|
} else {
|
||||||
|
return await User.update({_id: user._id}, {$set: set}).exec();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = async function processUsers () {
|
||||||
|
let query = {
|
||||||
|
migration: {$ne: MIGRATION_NAME},
|
||||||
|
};
|
||||||
|
|
||||||
|
const fields = {
|
||||||
|
_id: 1,
|
||||||
|
items: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
while (true) { // eslint-disable-line no-constant-condition
|
||||||
|
const users = await User // eslint-disable-line no-await-in-loop
|
||||||
|
.find(query)
|
||||||
|
.limit(250)
|
||||||
|
.sort({_id: 1})
|
||||||
|
.select(fields)
|
||||||
|
.lean()
|
||||||
|
.exec();
|
||||||
|
|
||||||
|
if (users.length === 0) {
|
||||||
|
console.warn('All appropriate users found and modified.');
|
||||||
|
console.warn(`\n${count} users processed\n`);
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
query._id = {
|
||||||
|
$gt: users[users.length - 1],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
|
||||||
|
}
|
||||||
|
};
|
||||||
110
migrations/archive/2018/20181231_nye.js
Normal file
110
migrations/archive/2018/20181231_nye.js
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
/* eslint-disable no-console */
|
||||||
|
const MIGRATION_NAME = '20181231_nye';
|
||||||
|
import { model as User } from '../../../website/server/models/user';
|
||||||
|
import mongoose from 'mongoose';
|
||||||
|
import { v4 as uuid } from 'uuid';
|
||||||
|
|
||||||
|
const progressCount = 1000;
|
||||||
|
let count = 0;
|
||||||
|
|
||||||
|
async function updateUser (user) {
|
||||||
|
count++;
|
||||||
|
|
||||||
|
const set = {'flags.newStuff': true};
|
||||||
|
let push;
|
||||||
|
|
||||||
|
set.migration = MIGRATION_NAME;
|
||||||
|
|
||||||
|
if (typeof user.items.gear.owned.head_special_nye2017 !== 'undefined') {
|
||||||
|
set['items.gear.owned.head_special_nye2018'] = false;
|
||||||
|
push = [
|
||||||
|
{
|
||||||
|
type: 'marketGear',
|
||||||
|
path: 'gear.flat.head_special_nye2018',
|
||||||
|
_id: uuid(),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
} else if (typeof user.items.gear.owned.head_special_nye2016 !== 'undefined') {
|
||||||
|
set['items.gear.owned.head_special_nye2017'] = false;
|
||||||
|
push = [
|
||||||
|
{
|
||||||
|
type: 'marketGear',
|
||||||
|
path: 'gear.flat.head_special_nye2017',
|
||||||
|
_id: uuid(),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
} else if (typeof user.items.gear.owned.head_special_nye2015 !== 'undefined') {
|
||||||
|
set['items.gear.owned.head_special_nye2016'] = false;
|
||||||
|
push = [
|
||||||
|
{
|
||||||
|
type: 'marketGear',
|
||||||
|
path: 'gear.flat.head_special_nye2016',
|
||||||
|
_id: uuid(),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
} else if (typeof user.items.gear.owned.head_special_nye2014 !== 'undefined') {
|
||||||
|
set['items.gear.owned.head_special_nye2015'] = false;
|
||||||
|
push = [
|
||||||
|
{
|
||||||
|
type: 'marketGear',
|
||||||
|
path: 'gear.flat.head_special_nye2015',
|
||||||
|
_id: uuid(),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
} else if (typeof user.items.gear.owned.head_special_nye !== 'undefined') {
|
||||||
|
set['items.gear.owned.head_special_nye2014'] = false;
|
||||||
|
push = [
|
||||||
|
{
|
||||||
|
type: 'marketGear',
|
||||||
|
path: 'gear.flat.head_special_nye2014',
|
||||||
|
_id: uuid(),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
set['items.gear.owned.head_special_nye'] = false;
|
||||||
|
push = [
|
||||||
|
{
|
||||||
|
type: 'marketGear',
|
||||||
|
path: 'gear.flat.head_special_nye',
|
||||||
|
_id: uuid(),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
|
||||||
|
|
||||||
|
return await User.update({_id: user._id}, {$set: set, $push: {pinnedItems: {$each: push}}}).exec();
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = async function processUsers () {
|
||||||
|
let query = {
|
||||||
|
migration: {$ne: MIGRATION_NAME},
|
||||||
|
};
|
||||||
|
|
||||||
|
const fields = {
|
||||||
|
_id: 1,
|
||||||
|
items: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
while (true) { // eslint-disable-line no-constant-condition
|
||||||
|
const users = await User // eslint-disable-line no-await-in-loop
|
||||||
|
.find(query)
|
||||||
|
.limit(250)
|
||||||
|
.sort({_id: 1})
|
||||||
|
.select(fields)
|
||||||
|
.lean()
|
||||||
|
.exec();
|
||||||
|
|
||||||
|
if (users.length === 0) {
|
||||||
|
console.warn('All appropriate users found and modified.');
|
||||||
|
console.warn(`\n${count} users processed\n`);
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
query._id = {
|
||||||
|
$gt: users[users.length - 1],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
If you need to use a migration from this folder, move it to /migrations.
|
If you need to use a migration from this folder, move it to /migrations.
|
||||||
|
|
||||||
Note that /migrations files (excluding /archive) are linted, so to pass test you'll have to make sure
|
Note that /migrations files (excluding /archive) are linted, so to pass test you'll have to make sure
|
||||||
that the file is written correctly.
|
that the file is written correctly.
|
||||||
|
|
||||||
|
They might also be using some old deps that we don't use anymore like Bluebird, mongoskin, ...
|
||||||
110
migrations/archive/mystery-items-old.js
Normal file
110
migrations/archive/mystery-items-old.js
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
import monk from 'monk';
|
||||||
|
import nconf from 'nconf';
|
||||||
|
|
||||||
|
const migrationName = 'mystery-items-201808.js'; // Update per month
|
||||||
|
const authorName = 'Sabe'; // in case script author needs to know when their ...
|
||||||
|
const authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; // ... own data is done
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Award this month's mystery items to subscribers
|
||||||
|
*/
|
||||||
|
const MYSTERY_ITEMS = ['armor_mystery_201810', 'head_mystery_201810'];
|
||||||
|
const CONNECTION_STRING = nconf.get('MIGRATION_CONNECT_STRING');
|
||||||
|
|
||||||
|
let dbUsers = monk(CONNECTION_STRING).get('users', { castIds: false });
|
||||||
|
let UserNotification = require('../../website/server/models/userNotification').model;
|
||||||
|
|
||||||
|
function processUsers (lastId) {
|
||||||
|
// specify a query to limit the affected users (empty for all users):
|
||||||
|
let query = {
|
||||||
|
migration: {$ne: migrationName},
|
||||||
|
'purchased.plan.customerId': { $ne: null },
|
||||||
|
$or: [
|
||||||
|
{ 'purchased.plan.dateTerminated': { $gte: new Date() } },
|
||||||
|
{ 'purchased.plan.dateTerminated': { $exists: false } },
|
||||||
|
{ 'purchased.plan.dateTerminated': { $eq: null } },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
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):
|
||||||
|
})
|
||||||
|
.then(updateUsers)
|
||||||
|
.catch((err) => {
|
||||||
|
console.log(err);
|
||||||
|
return exiting(1, `ERROR! ${ err}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let progressCount = 1000;
|
||||||
|
let count = 0;
|
||||||
|
|
||||||
|
function updateUsers (users) {
|
||||||
|
if (!users || users.length === 0) {
|
||||||
|
console.warn('All appropriate users found and modified.');
|
||||||
|
displayData();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let userPromises = users.map(updateUser);
|
||||||
|
let lastUser = users[users.length - 1];
|
||||||
|
|
||||||
|
return Promise.all(userPromises)
|
||||||
|
.then(() => {
|
||||||
|
processUsers(lastUser._id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateUser (user) {
|
||||||
|
count++;
|
||||||
|
|
||||||
|
const addToSet = {
|
||||||
|
'purchased.plan.mysteryItems': {
|
||||||
|
$each: MYSTERY_ITEMS,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const push = {
|
||||||
|
notifications: (new UserNotification({
|
||||||
|
type: 'NEW_MYSTERY_ITEMS',
|
||||||
|
data: {
|
||||||
|
MYSTERY_ITEMS,
|
||||||
|
},
|
||||||
|
})).toJSON(),
|
||||||
|
};
|
||||||
|
|
||||||
|
dbUsers.update({_id: user._id}, {$addToSet: addToSet, $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;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
let migrationName = '20180102_takeThis.js'; // Update per month
|
let migrationName = '20180904_takeThis.js'; // Update per month
|
||||||
let authorName = 'Sabe'; // in case script author needs to know when their ...
|
let authorName = 'Sabe'; // in case script author needs to know when their ...
|
||||||
let authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; // ... own data is done
|
let authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; // ... own data is done
|
||||||
|
|
||||||
@@ -6,15 +6,16 @@ let authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; // ... own data is done
|
|||||||
* Award Take This ladder items to participants in this month's challenge
|
* Award Take This ladder items to participants in this month's challenge
|
||||||
*/
|
*/
|
||||||
|
|
||||||
let monk = require('monk');
|
import monk from 'monk';
|
||||||
let connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
|
import nconf from 'nconf';
|
||||||
let dbUsers = monk(connectionString).get('users', { castIds: false });
|
const CONNECTION_STRING = nconf.get('MIGRATION_CONNECT_STRING'); // FOR TEST DATABASE
|
||||||
|
let dbUsers = monk(CONNECTION_STRING).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):
|
||||||
let query = {
|
let query = {
|
||||||
migration: {$ne: migrationName},
|
migration: {$ne: migrationName},
|
||||||
challenges: {$in: ['5f70ce5b-2d82-4114-8e44-ca65615aae62']}, // Update per month
|
challenges: {$in: ['1044ec0c-4a85-48c5-9f36-d51c0c62c7d3']}, // Update per month
|
||||||
};
|
};
|
||||||
|
|
||||||
if (lastId) {
|
if (lastId) {
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
import Bluebird from 'Bluebird';
|
|
||||||
|
|
||||||
import { model as Challenges } from '../../website/server/models/challenge';
|
import { model as Challenges } from '../../website/server/models/challenge';
|
||||||
import { model as User } from '../../website/server/models/user';
|
import { model as User } from '../../website/server/models/user';
|
||||||
|
|
||||||
@@ -17,10 +15,10 @@ async function syncChallengeToMembers (challenges) {
|
|||||||
promises.push(user.save());
|
promises.push(user.save());
|
||||||
});
|
});
|
||||||
|
|
||||||
return Bluebird.all(promises);
|
return Promise.all(promises);
|
||||||
});
|
});
|
||||||
|
|
||||||
return await Bluebird.all(challengSyncPromises);
|
return await Promise.all(challengSyncPromises);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function syncChallenges (lastChallengeDate) {
|
async function syncChallenges (lastChallengeDate) {
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import Bluebird from 'bluebird';
|
|
||||||
|
|
||||||
import { model as Group } from '../../website/server/models/group';
|
import { model as Group } from '../../website/server/models/group';
|
||||||
import { model as User } from '../../website/server/models/user';
|
import { model as User } from '../../website/server/models/user';
|
||||||
|
|
||||||
@@ -16,7 +14,7 @@ async function createGroup (name, privacy, type, leaderId) {
|
|||||||
group.leader = user._id;
|
group.leader = user._id;
|
||||||
user.guilds.push(group._id);
|
user.guilds.push(group._id);
|
||||||
|
|
||||||
return Bluebird.all([group.save(), user.save()]);
|
return Promise.all([group.save(), user.save()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = async function groupCreator () {
|
module.exports = async function groupCreator () {
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
/*
|
/*
|
||||||
* This migration will find users with unlimited subscriptions who are also eligible for Jackalope mounts, and award them
|
* This migration will find users with unlimited subscriptions who are also eligible for Jackalope mounts, and award them
|
||||||
*/
|
*/
|
||||||
import Bluebird from 'bluebird';
|
|
||||||
|
|
||||||
import { model as Group } from '../../website/server/models/group';
|
import { model as Group } from '../../website/server/models/group';
|
||||||
import { model as User } from '../../website/server/models/user';
|
import { model as User } from '../../website/server/models/user';
|
||||||
@@ -38,7 +37,7 @@ async function handOutJackalopes () {
|
|||||||
|
|
||||||
cursor.on('close', async () => {
|
cursor.on('close', async () => {
|
||||||
console.log('done');
|
console.log('done');
|
||||||
return await Bluebird.all(promises);
|
return await Promise.all(promises);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
52
migrations/groups/migrate-chat.js
Normal file
52
migrations/groups/migrate-chat.js
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
// @migrationName = 'MigrateGroupChat';
|
||||||
|
// @authorName = 'TheHollidayInn'; // in case script author needs to know when their ...
|
||||||
|
// @authorUuid = ''; // ... own data is done
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This migration moves chat off of groups and into their own model
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { model as Group } from '../../website/server/models/group';
|
||||||
|
import { model as Chat } from '../../website/server/models/chat';
|
||||||
|
|
||||||
|
async function moveGroupChatToModel (skip = 0) {
|
||||||
|
const groups = await Group.find({})
|
||||||
|
.limit(50)
|
||||||
|
.skip(skip)
|
||||||
|
.sort({ _id: -1 })
|
||||||
|
.exec();
|
||||||
|
|
||||||
|
if (groups.length === 0) {
|
||||||
|
console.log('End of groups');
|
||||||
|
process.exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
const promises = groups.map(group => {
|
||||||
|
const chatpromises = group.chat.map(message => {
|
||||||
|
const newChat = new Chat();
|
||||||
|
Object.assign(newChat, message);
|
||||||
|
newChat._id = message.id;
|
||||||
|
newChat.groupId = group._id;
|
||||||
|
|
||||||
|
return newChat.save();
|
||||||
|
});
|
||||||
|
|
||||||
|
group.chat = [];
|
||||||
|
chatpromises.push(group.save());
|
||||||
|
|
||||||
|
return chatpromises;
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
const reducedPromises = promises.reduce((acc, curr) => {
|
||||||
|
acc = acc.concat(curr);
|
||||||
|
return acc;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
console.log(reducedPromises);
|
||||||
|
await Promise.all(reducedPromises);
|
||||||
|
moveGroupChatToModel(skip + 50);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = moveGroupChatToModel;
|
||||||
107
migrations/groups/reconcile-group-plan-members.js
Normal file
107
migrations/groups/reconcile-group-plan-members.js
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
import monk from 'monk';
|
||||||
|
import nconf from 'nconf';
|
||||||
|
import stripePayments from '../../website/server/libs/payments/stripe';
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Ensure that group plan billing is accurate by doing the following:
|
||||||
|
* 1. Correct the memberCount in all paid groups whose counts are wrong
|
||||||
|
* 2. Where the above uses Stripe, update their subscription counts in Stripe
|
||||||
|
*
|
||||||
|
* Provides output on what groups were fixed, which can be piped to CSV.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const CONNECTION_STRING = nconf.get('MIGRATION_CONNECT_STRING');
|
||||||
|
|
||||||
|
let dbGroups = monk(CONNECTION_STRING).get('groups', { castIds: false });
|
||||||
|
let dbUsers = monk(CONNECTION_STRING).get('users', { castIds: false });
|
||||||
|
|
||||||
|
async function fixGroupPlanMembers () {
|
||||||
|
console.info('Group ID, Customer ID, Plan ID, Quantity, Recorded Member Count, Actual Member Count');
|
||||||
|
let groupPlanCount = 0;
|
||||||
|
let fixedGroupCount = 0;
|
||||||
|
|
||||||
|
dbGroups.find(
|
||||||
|
{
|
||||||
|
$and:
|
||||||
|
[
|
||||||
|
{'purchased.plan.planId': {$ne: null}},
|
||||||
|
{'purchased.plan.planId': {$ne: ''}},
|
||||||
|
{'purchased.plan.customerId': {$ne: 'cus_9f0DV4g7WHRzpM'}}, // Demo groups
|
||||||
|
{'purchased.plan.customerId': {$ne: 'cus_9maalqDOFTrvqx'}},
|
||||||
|
],
|
||||||
|
$or:
|
||||||
|
[
|
||||||
|
{'purchased.plan.dateTerminated': null},
|
||||||
|
{'purchased.plan.dateTerminated': ''},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fields: {
|
||||||
|
memberCount: 1,
|
||||||
|
'purchased.plan': 1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
).each(async (group, {close, pause, resume}) => { // eslint-disable-line no-unused-vars
|
||||||
|
pause();
|
||||||
|
groupPlanCount++;
|
||||||
|
|
||||||
|
const canonicalMemberCount = await dbUsers.count(
|
||||||
|
{
|
||||||
|
$or:
|
||||||
|
[
|
||||||
|
{'party._id': group._id},
|
||||||
|
{guilds: group._id},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const incorrectMemberCount = group.memberCount !== canonicalMemberCount;
|
||||||
|
|
||||||
|
const isMonthlyPlan = group.purchased.plan.planId === 'group_monthly';
|
||||||
|
const quantityMismatch = group.purchased.plan.quantity !== group.memberCount + 2;
|
||||||
|
const incorrectQuantity = isMonthlyPlan && quantityMismatch;
|
||||||
|
|
||||||
|
if (!incorrectMemberCount && !incorrectQuantity) {
|
||||||
|
resume();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.info(`${group._id}, ${group.purchased.plan.customerId}, ${group.purchased.plan.planId}, ${group.purchased.plan.quantity}, ${group.memberCount}, ${canonicalMemberCount}`);
|
||||||
|
|
||||||
|
const groupUpdate = await dbGroups.update(
|
||||||
|
{ _id: group._id },
|
||||||
|
{
|
||||||
|
$set: {
|
||||||
|
memberCount: canonicalMemberCount,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!groupUpdate) return;
|
||||||
|
|
||||||
|
fixedGroupCount++;
|
||||||
|
if (group.purchased.plan.paymentMethod === 'Stripe') {
|
||||||
|
await stripePayments.chargeForAdditionalGroupMember(group);
|
||||||
|
await dbGroups.update(
|
||||||
|
{_id: group._id},
|
||||||
|
{$set: {'purchased.plan.quantity': canonicalMemberCount + 2}}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (incorrectQuantity) {
|
||||||
|
await dbGroups.update(
|
||||||
|
{_id: group._id},
|
||||||
|
{$set: {'purchased.plan.quantity': canonicalMemberCount + 2}}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
resume();
|
||||||
|
}).then(() => {
|
||||||
|
console.info(`Fixed ${fixedGroupCount} out of ${groupPlanCount} active Group Plans`);
|
||||||
|
return process.exit(0);
|
||||||
|
}).catch((err) => {
|
||||||
|
console.log(err);
|
||||||
|
return process.exit(1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = fixGroupPlanMembers;
|
||||||
@@ -9,8 +9,6 @@ let authorUuid = ''; // ... own data is done
|
|||||||
* subscription to all members
|
* subscription to all members
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Bluebird from 'bluebird';
|
|
||||||
|
|
||||||
import { model as Group } from '../../website/server/models/group';
|
import { model as Group } from '../../website/server/models/group';
|
||||||
import * as payments from '../../website/server/libs/payments';
|
import * as payments from '../../website/server/libs/payments';
|
||||||
|
|
||||||
@@ -28,7 +26,7 @@ async function updateGroupsWithGroupPlans () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
cursor.on('close', async () => {
|
cursor.on('close', async () => {
|
||||||
return await Bluebird.all(promises);
|
return await Promise.all(promises);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
require('babel-register');
|
require('babel-register');
|
||||||
require('babel-polyfill');
|
|
||||||
|
|
||||||
// This file must use ES5, everything required can be in ES6
|
// This file must use ES5, everything required can be in ES6
|
||||||
|
|
||||||
function setUpServer () {
|
function setUpServer () {
|
||||||
const nconf = require('nconf'); // eslint-disable-line global-require, no-unused-vars
|
const nconf = require('nconf'); // eslint-disable-line global-require, no-unused-vars
|
||||||
const mongoose = require('mongoose'); // eslint-disable-line global-require, no-unused-vars
|
const mongoose = require('mongoose'); // eslint-disable-line global-require, no-unused-vars
|
||||||
const Bluebird = require('bluebird'); // eslint-disable-line global-require, no-unused-vars
|
|
||||||
const setupNconf = require('../website/server/libs/setupNconf'); // eslint-disable-line global-require
|
const setupNconf = require('../website/server/libs/setupNconf'); // eslint-disable-line global-require
|
||||||
|
|
||||||
setupNconf();
|
setupNconf();
|
||||||
|
|
||||||
// We require src/server and npt src/index because
|
// We require src/server and npt src/index because
|
||||||
// 1. nconf is already setup
|
// 1. nconf is already setup
|
||||||
// 2. we don't need clustering
|
// 2. we don't need clustering
|
||||||
@@ -17,5 +17,12 @@ function setUpServer () {
|
|||||||
setUpServer();
|
setUpServer();
|
||||||
|
|
||||||
// Replace this with your migration
|
// Replace this with your migration
|
||||||
const processUsers = require('./20180125_clean_new_notifications.js');
|
const processUsers = require('./archive/2018/20181231_nye.js');
|
||||||
processUsers();
|
processUsers()
|
||||||
|
.then(function success () {
|
||||||
|
process.exit(0);
|
||||||
|
})
|
||||||
|
.catch(function failure (err) {
|
||||||
|
console.log(err);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
let Bluebird = require('bluebird');
|
|
||||||
let request = require('superagent');
|
let request = require('superagent');
|
||||||
let last = require('lodash/last');
|
let last = require('lodash/last');
|
||||||
let AWS = require('aws-sdk');
|
let AWS = require('aws-sdk');
|
||||||
@@ -74,7 +73,7 @@ function uploadToS3 (start, end, filesUrls) {
|
|||||||
});
|
});
|
||||||
console.log(promises.length);
|
console.log(promises.length);
|
||||||
|
|
||||||
return Bluebird.all(promises)
|
return Promise.all(promises)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
currentIndex += 50;
|
currentIndex += 50;
|
||||||
uploadToS3(currentIndex, currentIndex + 50, filesUrls);
|
uploadToS3(currentIndex, currentIndex + 50, filesUrls);
|
||||||
|
|||||||
138
migrations/tasks/habits-one-history-entry-per-day-challenges.js
Normal file
138
migrations/tasks/habits-one-history-entry-per-day-challenges.js
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
// const migrationName = 'habits-one-history-entry-per-day';
|
||||||
|
// const authorName = 'paglias'; // in case script author needs to know when their ...
|
||||||
|
// const authorUuid = 'ed4c688c-6652-4a92-9d03-a5a79844174a'; // ... own data is done
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Iterates over all habits and condense multiple history entries for the same day into a single entry
|
||||||
|
*/
|
||||||
|
|
||||||
|
const monk = require('monk');
|
||||||
|
const _ = require('lodash');
|
||||||
|
const moment = require('moment');
|
||||||
|
const connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
|
||||||
|
const dbTasks = monk(connectionString).get('tasks', { castIds: false });
|
||||||
|
|
||||||
|
function processChallengeHabits (lastId) {
|
||||||
|
let query = {
|
||||||
|
'challenge.id': {$exists: true},
|
||||||
|
userId: {$exists: false},
|
||||||
|
type: 'habit',
|
||||||
|
};
|
||||||
|
|
||||||
|
if (lastId) {
|
||||||
|
query._id = {
|
||||||
|
$gt: lastId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
dbTasks.find(query, {
|
||||||
|
sort: {_id: 1},
|
||||||
|
limit: 500,
|
||||||
|
})
|
||||||
|
.then(updateChallengeHabits)
|
||||||
|
.catch((err) => {
|
||||||
|
console.log(err);
|
||||||
|
return exiting(1, `ERROR! ${ err}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let progressCount = 1000;
|
||||||
|
let count = 0;
|
||||||
|
|
||||||
|
function updateChallengeHabits (habits) {
|
||||||
|
if (!habits || habits.length === 0) {
|
||||||
|
console.warn('All appropriate challenge habits found and modified.');
|
||||||
|
displayData();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let habitsPromises = habits.map(updateChallengeHabit);
|
||||||
|
let lastHabit = habits[habits.length - 1];
|
||||||
|
|
||||||
|
return Promise.all(habitsPromises)
|
||||||
|
.then(() => {
|
||||||
|
return processChallengeHabits(lastHabit._id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateChallengeHabit (habit) {
|
||||||
|
count++;
|
||||||
|
|
||||||
|
if (habit && habit.history && habit.history.length > 0) {
|
||||||
|
// First remove missing entries
|
||||||
|
habit.history = habit.history.filter(entry => Boolean(entry));
|
||||||
|
|
||||||
|
habit.history = _.chain(habit.history)
|
||||||
|
// processes all entries to identify an up or down score
|
||||||
|
.forEach((entry, index) => {
|
||||||
|
if (index === 0) { // first entry doesn't have a previous one
|
||||||
|
// first value < 0 identifies a negative score as the first action
|
||||||
|
entry.scoreDirection = entry.value >= 0 ? 'up' : 'down';
|
||||||
|
} else {
|
||||||
|
// could be missing if the previous entry was null and thus excluded
|
||||||
|
const previousEntry = habit.history[index - 1];
|
||||||
|
const previousValue = previousEntry.value;
|
||||||
|
|
||||||
|
entry.scoreDirection = entry.value > previousValue ? 'up' : 'down';
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.groupBy(entry => { // group entries by aggregateBy
|
||||||
|
return moment(entry.date).format('YYYYMMDD');
|
||||||
|
})
|
||||||
|
.toPairs() // [key, entry]
|
||||||
|
.sortBy(([key]) => key) // sort by date
|
||||||
|
.map(keyEntryPair => {
|
||||||
|
let entries = keyEntryPair[1]; // 1 is entry, 0 is key
|
||||||
|
let scoredUp = 0;
|
||||||
|
let scoredDown = 0;
|
||||||
|
|
||||||
|
entries.forEach(entry => {
|
||||||
|
if (entry.scoreDirection === 'up') {
|
||||||
|
scoredUp += 1;
|
||||||
|
} else {
|
||||||
|
scoredDown += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete the unnecessary scoreDirection and scoreNotes prop
|
||||||
|
delete entry.scoreDirection;
|
||||||
|
delete entry.scoreNotes;
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
date: Number(entries[entries.length - 1].date), // keep last value
|
||||||
|
value: entries[entries.length - 1].value, // keep last value,
|
||||||
|
scoredUp,
|
||||||
|
scoredDown,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.value();
|
||||||
|
|
||||||
|
return dbTasks.update({_id: habit._id}, {
|
||||||
|
$set: {history: habit.history},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count % progressCount === 0) console.warn(`${count } habits 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 = processChallengeHabits;
|
||||||
163
migrations/tasks/habits-one-history-entry-per-day-users.js
Normal file
163
migrations/tasks/habits-one-history-entry-per-day-users.js
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
const migrationName = 'habits-one-history-entry-per-day';
|
||||||
|
const authorName = 'paglias'; // in case script author needs to know when their ...
|
||||||
|
const authorUuid = 'ed4c688c-6652-4a92-9d03-a5a79844174a'; // ... own data is done
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Iterates over all habits and condense multiple history entries for the same day into a single entry
|
||||||
|
*/
|
||||||
|
|
||||||
|
const monk = require('monk');
|
||||||
|
const _ = require('lodash');
|
||||||
|
const moment = require('moment');
|
||||||
|
const connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
|
||||||
|
const dbTasks = monk(connectionString).get('tasks', { castIds: false });
|
||||||
|
const dbUsers = monk(connectionString).get('users', { castIds: false });
|
||||||
|
|
||||||
|
function processUsers (lastId) {
|
||||||
|
let query = {
|
||||||
|
migration: {$ne: migrationName},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (lastId) {
|
||||||
|
query._id = {
|
||||||
|
$gt: lastId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
dbUsers.find(query, {
|
||||||
|
sort: {_id: 1},
|
||||||
|
limit: 50, // just 50 users per time since we have to process all their habits as well
|
||||||
|
fields: ['_id', 'preferences.timezoneOffset', 'preferences.dayStart'],
|
||||||
|
})
|
||||||
|
.then(updateUsers)
|
||||||
|
.catch((err) => {
|
||||||
|
console.log(err);
|
||||||
|
return exiting(1, `ERROR! ${ err}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let progressCount = 1000;
|
||||||
|
let count = 0;
|
||||||
|
|
||||||
|
function updateUsers (users) {
|
||||||
|
if (!users || users.length === 0) {
|
||||||
|
console.warn('All appropriate users and their tasks found and modified.');
|
||||||
|
displayData();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let usersPromises = users.map(updateUser);
|
||||||
|
let lastUser = users[users.length - 1];
|
||||||
|
|
||||||
|
return Promise.all(usersPromises)
|
||||||
|
.then(() => {
|
||||||
|
return processUsers(lastUser._id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateHabit (habit, timezoneOffset, dayStart) {
|
||||||
|
if (habit && habit.history && habit.history.length > 0) {
|
||||||
|
// First remove missing entries
|
||||||
|
habit.history = habit.history.filter(entry => Boolean(entry));
|
||||||
|
|
||||||
|
habit.history = _.chain(habit.history)
|
||||||
|
// processes all entries to identify an up or down score
|
||||||
|
.forEach((entry, index) => {
|
||||||
|
if (index === 0) { // first entry doesn't have a previous one
|
||||||
|
// first value < 0 identifies a negative score as the first action
|
||||||
|
entry.scoreDirection = entry.value >= 0 ? 'up' : 'down';
|
||||||
|
} else {
|
||||||
|
// could be missing if the previous entry was null and thus excluded
|
||||||
|
const previousEntry = habit.history[index - 1];
|
||||||
|
const previousValue = previousEntry.value;
|
||||||
|
|
||||||
|
entry.scoreDirection = entry.value > previousValue ? 'up' : 'down';
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.groupBy(entry => { // group entries by aggregateBy
|
||||||
|
const entryDate = moment(entry.date).zone(timezoneOffset || 0);
|
||||||
|
if (entryDate.hour() < dayStart) entryDate.subtract(1, 'day');
|
||||||
|
return entryDate.format('YYYYMMDD');
|
||||||
|
})
|
||||||
|
.toPairs() // [key, entry]
|
||||||
|
.sortBy(([key]) => key) // sort by date
|
||||||
|
.map(keyEntryPair => {
|
||||||
|
let entries = keyEntryPair[1]; // 1 is entry, 0 is key
|
||||||
|
let scoredUp = 0;
|
||||||
|
let scoredDown = 0;
|
||||||
|
|
||||||
|
entries.forEach(entry => {
|
||||||
|
if (entry.scoreDirection === 'up') {
|
||||||
|
scoredUp += 1;
|
||||||
|
} else {
|
||||||
|
scoredDown += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete the unnecessary scoreDirection and scoreNotes prop
|
||||||
|
delete entry.scoreDirection;
|
||||||
|
delete entry.scoreNotes;
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
date: Number(entries[entries.length - 1].date), // keep last value
|
||||||
|
value: entries[entries.length - 1].value, // keep last value,
|
||||||
|
scoredUp,
|
||||||
|
scoredDown,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.value();
|
||||||
|
|
||||||
|
return dbTasks.update({_id: habit._id}, {
|
||||||
|
$set: {history: habit.history},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateUser (user) {
|
||||||
|
count++;
|
||||||
|
|
||||||
|
const timezoneOffset = user.preferences.timezoneOffset;
|
||||||
|
const dayStart = user.preferences.dayStart;
|
||||||
|
|
||||||
|
if (count % progressCount === 0) console.warn(`${count } ${ user._id}`);
|
||||||
|
if (user._id === authorUuid) console.warn(`${authorName } being processed`);
|
||||||
|
|
||||||
|
return dbTasks.find({
|
||||||
|
type: 'habit',
|
||||||
|
userId: user._id,
|
||||||
|
})
|
||||||
|
.then(habits => {
|
||||||
|
return Promise.all(habits.map(habit => updateHabit(habit, timezoneOffset, dayStart)));
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
return dbUsers.update({_id: user._id}, {
|
||||||
|
$set: {migration: migrationName},
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.log(err);
|
||||||
|
return exiting(1, `ERROR! ${ err}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = processUsers;
|
||||||
@@ -7,7 +7,7 @@ let authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; // ... own data is done
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
let monk = require('monk');
|
let monk = require('monk');
|
||||||
let connectionString = 'mongodb://sabrecat:z8e8jyRA8CTofMQ@ds013393-a0.mlab.com:13393/habitica?auto_reconnect=true';
|
let connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true';
|
||||||
let dbTasks = monk(connectionString).get('tasks', { castIds: false });
|
let dbTasks = monk(connectionString).get('tasks', { castIds: false });
|
||||||
|
|
||||||
function processTasks (lastId) {
|
function processTasks (lastId) {
|
||||||
|
|||||||
61
migrations/users/bulk-email.js
Normal file
61
migrations/users/bulk-email.js
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
/* eslint-disable no-console */
|
||||||
|
import { sendTxn } from '../../../website/server/libs/email';
|
||||||
|
import { model as User } from '../../website/server/models/user';
|
||||||
|
import moment from 'moment';
|
||||||
|
import nconf from 'nconf';
|
||||||
|
const BASE_URL = nconf.get('BASE_URL');
|
||||||
|
const EMAIL_SLUG = 'mandrill-email-slug'; // Set email template to send
|
||||||
|
const MIGRATION_NAME = 'bulk-email';
|
||||||
|
|
||||||
|
const progressCount = 1000;
|
||||||
|
let count = 0;
|
||||||
|
|
||||||
|
async function updateUser (user) {
|
||||||
|
count++;
|
||||||
|
|
||||||
|
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
|
||||||
|
|
||||||
|
sendTxn(
|
||||||
|
user,
|
||||||
|
EMAIL_SLUG,
|
||||||
|
[{name: 'BASE_URL', content: BASE_URL}] // Add variables from template
|
||||||
|
);
|
||||||
|
|
||||||
|
return await User.update({_id: user._id}, {$set: {migration: MIGRATION_NAME}}).exec();
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = async function processUsers () {
|
||||||
|
let query = {
|
||||||
|
migration: {$ne: MIGRATION_NAME},
|
||||||
|
'auth.timestamps.loggedin': {$gt: moment().subtract(2, 'weeks').toDate()}, // customize or remove to target different populations
|
||||||
|
};
|
||||||
|
|
||||||
|
const fields = {
|
||||||
|
_id: 1,
|
||||||
|
auth: 1,
|
||||||
|
preferences: 1,
|
||||||
|
profile: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
while (true) { // eslint-disable-line no-constant-condition
|
||||||
|
const users = await User // eslint-disable-line no-await-in-loop
|
||||||
|
.find(query)
|
||||||
|
.limit(250)
|
||||||
|
.sort({_id: 1})
|
||||||
|
.select(fields)
|
||||||
|
.lean()
|
||||||
|
.exec();
|
||||||
|
|
||||||
|
if (users.length === 0) {
|
||||||
|
console.warn('All appropriate users found and modified.');
|
||||||
|
console.warn(`\n${count} users processed\n`);
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
query._id = {
|
||||||
|
$gt: users[users.length - 1],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
|
||||||
|
}
|
||||||
|
};
|
||||||
117
migrations/users/full-stable.js
Normal file
117
migrations/users/full-stable.js
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
import each from 'lodash/each';
|
||||||
|
import keys from 'lodash/keys';
|
||||||
|
import content from '../../website/common/script/content/index';
|
||||||
|
const migrationName = 'full-stable.js';
|
||||||
|
const authorName = 'Sabe'; // in case script author needs to know when their ...
|
||||||
|
const authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; // ... own data is done
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Award users every extant pet and mount
|
||||||
|
*/
|
||||||
|
const connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
|
||||||
|
|
||||||
|
let monk = require('monk');
|
||||||
|
let dbUsers = monk(connectionString).get('users', { castIds: false });
|
||||||
|
|
||||||
|
function processUsers (lastId) {
|
||||||
|
// specify a query to limit the affected users (empty for all users):
|
||||||
|
let query = {
|
||||||
|
'profile.name': 'SabreCat',
|
||||||
|
};
|
||||||
|
|
||||||
|
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):
|
||||||
|
})
|
||||||
|
.then(updateUsers)
|
||||||
|
.catch((err) => {
|
||||||
|
console.log(err);
|
||||||
|
return exiting(1, `ERROR! ${ err}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let progressCount = 1000;
|
||||||
|
let count = 0;
|
||||||
|
|
||||||
|
function updateUsers (users) {
|
||||||
|
if (!users || users.length === 0) {
|
||||||
|
console.warn('All appropriate users found and modified.');
|
||||||
|
displayData();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let userPromises = users.map(updateUser);
|
||||||
|
let lastUser = users[users.length - 1];
|
||||||
|
|
||||||
|
return Promise.all(userPromises)
|
||||||
|
.then(() => {
|
||||||
|
processUsers(lastUser._id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateUser (user) {
|
||||||
|
count++;
|
||||||
|
let set = {
|
||||||
|
migration: migrationName,
|
||||||
|
};
|
||||||
|
|
||||||
|
each(keys(content.pets), (pet) => {
|
||||||
|
set[`items.pets.${pet}`] = 5;
|
||||||
|
});
|
||||||
|
each(keys(content.premiumPets), (pet) => {
|
||||||
|
set[`items.pets.${pet}`] = 5;
|
||||||
|
});
|
||||||
|
each(keys(content.questPets), (pet) => {
|
||||||
|
set[`items.pets.${pet}`] = 5;
|
||||||
|
});
|
||||||
|
each(keys(content.specialPets), (pet) => {
|
||||||
|
set[`items.pets.${pet}`] = 5;
|
||||||
|
});
|
||||||
|
each(keys(content.mounts), (mount) => {
|
||||||
|
set[`items.mounts.${mount}`] = true;
|
||||||
|
});
|
||||||
|
each(keys(content.premiumMounts), (mount) => {
|
||||||
|
set[`items.mounts.${mount}`] = true;
|
||||||
|
});
|
||||||
|
each(keys(content.questMounts), (mount) => {
|
||||||
|
set[`items.mounts.${mount}`] = true;
|
||||||
|
});
|
||||||
|
each(keys(content.specialMounts), (mount) => {
|
||||||
|
set[`items.mounts.${mount}`] = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
dbUsers.update({_id: user._id}, {$set: set});
|
||||||
|
|
||||||
|
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;
|
||||||
@@ -1,68 +1,13 @@
|
|||||||
const migrationName = 'mystery-items-201802.js'; // Update per month
|
/* eslint-disable no-console */
|
||||||
const authorName = 'Sabe'; // in case script author needs to know when their ...
|
const MIGRATION_NAME = 'mystery_items_201812';
|
||||||
const authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; // ... own data is done
|
const MYSTERY_ITEMS = ['headAccessory_mystery_201812', 'back_mystery_201812'];
|
||||||
|
import { model as User } from '../../website/server/models/user';
|
||||||
|
import { model as UserNotification } from '../../website/server/models/userNotification';
|
||||||
|
|
||||||
/*
|
const progressCount = 1000;
|
||||||
* Award this month's mystery items to subscribers
|
|
||||||
*/
|
|
||||||
const MYSTERY_ITEMS = ['armor_mystery_201802', 'head_mystery_201802', 'shield_mystery_201802'];
|
|
||||||
const connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
|
|
||||||
|
|
||||||
let monk = require('monk');
|
|
||||||
let dbUsers = monk(connectionString).get('users', { castIds: false });
|
|
||||||
let UserNotification = require('../../website/server/models/userNotification').model;
|
|
||||||
|
|
||||||
function processUsers (lastId) {
|
|
||||||
// specify a query to limit the affected users (empty for all users):
|
|
||||||
let query = {
|
|
||||||
migration: {$ne: migrationName},
|
|
||||||
'purchased.plan.customerId': { $ne: null },
|
|
||||||
$or: [
|
|
||||||
{ 'purchased.plan.dateTerminated': { $gte: new Date() } },
|
|
||||||
{ 'purchased.plan.dateTerminated': { $exists: false } },
|
|
||||||
{ 'purchased.plan.dateTerminated': { $eq: null } },
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
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):
|
|
||||||
})
|
|
||||||
.then(updateUsers)
|
|
||||||
.catch((err) => {
|
|
||||||
console.log(err);
|
|
||||||
return exiting(1, `ERROR! ${ err}`);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let progressCount = 1000;
|
|
||||||
let count = 0;
|
let count = 0;
|
||||||
|
|
||||||
function updateUsers (users) {
|
async function updateUser (user) {
|
||||||
if (!users || users.length === 0) {
|
|
||||||
console.warn('All appropriate users found and modified.');
|
|
||||||
displayData();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let userPromises = users.map(updateUser);
|
|
||||||
let lastUser = users[users.length - 1];
|
|
||||||
|
|
||||||
return Promise.all(userPromises)
|
|
||||||
.then(() => {
|
|
||||||
processUsers(lastUser._id);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateUser (user) {
|
|
||||||
count++;
|
count++;
|
||||||
|
|
||||||
const addToSet = {
|
const addToSet = {
|
||||||
@@ -78,31 +23,49 @@ function updateUser (user) {
|
|||||||
},
|
},
|
||||||
})).toJSON(),
|
})).toJSON(),
|
||||||
};
|
};
|
||||||
|
const set = {
|
||||||
|
migration: MIGRATION_NAME,
|
||||||
|
};
|
||||||
|
|
||||||
dbUsers.update({_id: user._id}, {$addToSet: addToSet, $push: push});
|
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
|
||||||
|
|
||||||
if (count % progressCount === 0) console.warn(`${count } ${ user._id}`);
|
return await User.update({_id: user._id}, {$set: set, $push: push, $addToSet: addToSet}).exec();
|
||||||
if (user._id === authorUuid) console.warn(`${authorName } processed`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function displayData () {
|
module.exports = async function processUsers () {
|
||||||
console.warn(`\n${ count } users processed\n`);
|
let query = {
|
||||||
return exiting(0);
|
migration: {$ne: MIGRATION_NAME},
|
||||||
}
|
'purchased.plan.customerId': { $ne: null },
|
||||||
|
$or: [
|
||||||
|
{ 'purchased.plan.dateTerminated': { $gte: new Date() } },
|
||||||
|
{ 'purchased.plan.dateTerminated': { $exists: false } },
|
||||||
|
{ 'purchased.plan.dateTerminated': { $eq: null } },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
function exiting (code, msg) {
|
const fields = {
|
||||||
code = code || 0; // 0 = success
|
_id: 1,
|
||||||
if (code && !msg) {
|
};
|
||||||
msg = 'ERROR!';
|
|
||||||
}
|
while (true) { // eslint-disable-line no-constant-condition
|
||||||
if (msg) {
|
const users = await User // eslint-disable-line no-await-in-loop
|
||||||
if (code) {
|
.find(query)
|
||||||
console.error(msg);
|
.limit(250)
|
||||||
} else {
|
.sort({_id: 1})
|
||||||
console.log(msg);
|
.select(fields)
|
||||||
|
.lean()
|
||||||
|
.exec();
|
||||||
|
|
||||||
|
if (users.length === 0) {
|
||||||
|
console.warn('All appropriate users found and modified.');
|
||||||
|
console.warn(`\n${count} users processed\n`);
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
query._id = {
|
||||||
|
$gt: users[users.length - 1],
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
|
||||||
process.exit(code);
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = processUsers;
|
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
109
migrations/users/remove-social-users-extra-data.js
Normal file
109
migrations/users/remove-social-users-extra-data.js
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
const migrationName = 'remove-social-users-extra-data.js';
|
||||||
|
const authorName = 'paglias'; // in case script author needs to know when their ...
|
||||||
|
const authorUuid = 'ed4c688c-6652-4a92-9d03-a5a79844174a'; // ... own data is done
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Remove not needed data from social profiles
|
||||||
|
*/
|
||||||
|
const connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
|
||||||
|
|
||||||
|
const monk = require('monk');
|
||||||
|
const dbUsers = monk(connectionString).get('users', { castIds: false });
|
||||||
|
|
||||||
|
function processUsers (lastId) {
|
||||||
|
// specify a query to limit the affected users (empty for all users):
|
||||||
|
let query = {
|
||||||
|
migration: {$ne: migrationName},
|
||||||
|
$or: [
|
||||||
|
{ 'auth.facebook.id': { $exists: true } },
|
||||||
|
{ 'auth.google.id': { $exists: true } },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
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}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let progressCount = 1000;
|
||||||
|
let count = 0;
|
||||||
|
|
||||||
|
function updateUsers (users) {
|
||||||
|
if (!users || users.length === 0) {
|
||||||
|
console.warn('All appropriate users found and modified.');
|
||||||
|
displayData();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let userPromises = users.map(updateUser);
|
||||||
|
let lastUser = users[users.length - 1];
|
||||||
|
|
||||||
|
return Promise.all(userPromises)
|
||||||
|
.then(() => {
|
||||||
|
processUsers(lastUser._id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateUser (user) {
|
||||||
|
count++;
|
||||||
|
|
||||||
|
const isFacebook = user.auth.facebook && user.auth.facebook.id;
|
||||||
|
const isGoogle = user.auth.google && user.auth.google.id;
|
||||||
|
|
||||||
|
const update = { $set: {} };
|
||||||
|
|
||||||
|
if (isFacebook) {
|
||||||
|
update.$set['auth.facebook'] = {
|
||||||
|
id: user.auth.facebook.id,
|
||||||
|
emails: user.auth.facebook.emails,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isGoogle) {
|
||||||
|
update.$set['auth.google'] = {
|
||||||
|
id: user.auth.google.id,
|
||||||
|
emails: user.auth.google.emails,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
dbUsers.update({
|
||||||
|
_id: user._id,
|
||||||
|
}, update);
|
||||||
|
|
||||||
|
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;
|
||||||
81
migrations/users/take-this.js
Normal file
81
migrations/users/take-this.js
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
/* eslint-disable no-console */
|
||||||
|
const MIGRATION_NAME = '20181203_take_this';
|
||||||
|
import { v4 as uuid } from 'uuid';
|
||||||
|
|
||||||
|
import { model as User } from '../../website/server/models/user';
|
||||||
|
|
||||||
|
const progressCount = 1000;
|
||||||
|
let count = 0;
|
||||||
|
|
||||||
|
async function updateUser (user) {
|
||||||
|
count++;
|
||||||
|
|
||||||
|
const set = {};
|
||||||
|
let push;
|
||||||
|
|
||||||
|
set.migration = MIGRATION_NAME;
|
||||||
|
|
||||||
|
if (typeof user.items.gear.owned.back_special_takeThis !== 'undefined') {
|
||||||
|
push = false;
|
||||||
|
} else if (typeof user.items.gear.owned.body_special_takeThis !== 'undefined') {
|
||||||
|
set['items.gear.owned.back_special_takeThis'] = false;
|
||||||
|
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.back_special_takeThis', _id: uuid()}};
|
||||||
|
} else if (typeof user.items.gear.owned.head_special_takeThis !== 'undefined') {
|
||||||
|
set['items.gear.owned.body_special_takeThis'] = false;
|
||||||
|
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.body_special_takeThis', _id: uuid()}};
|
||||||
|
} else if (typeof user.items.gear.owned.armor_special_takeThis !== 'undefined') {
|
||||||
|
set['items.gear.owned.head_special_takeThis'] = false;
|
||||||
|
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.head_special_takeThis', _id: uuid()}};
|
||||||
|
} else if (typeof user.items.gear.owned.weapon_special_takeThis !== 'undefined') {
|
||||||
|
set['items.gear.owned.armor_special_takeThis'] = false;
|
||||||
|
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.armor_special_takeThis', _id: uuid()}};
|
||||||
|
} else if (typeof user.items.gear.owned.shield_special_takeThis !== 'undefined') {
|
||||||
|
set['items.gear.owned.weapon_special_takeThis'] = false;
|
||||||
|
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.weapon_special_takeThis', _id: uuid()}};
|
||||||
|
} else {
|
||||||
|
set['items.gear.owned.shield_special_takeThis'] = false;
|
||||||
|
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.shield_special_takeThis', _id: uuid()}};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
|
||||||
|
|
||||||
|
if (push) {
|
||||||
|
return await User.update({_id: user._id}, {$set: set, $push: push}).exec();
|
||||||
|
} else {
|
||||||
|
return await User.update({_id: user._id}, {$set: set}).exec();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = async function processUsers () {
|
||||||
|
let query = {
|
||||||
|
migration: {$ne: MIGRATION_NAME},
|
||||||
|
challenges: '00708425-d477-41a5-bf27-6270466e7976',
|
||||||
|
};
|
||||||
|
|
||||||
|
const fields = {
|
||||||
|
_id: 1,
|
||||||
|
items: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
while (true) { // eslint-disable-line no-constant-condition
|
||||||
|
const users = await User // eslint-disable-line no-await-in-loop
|
||||||
|
.find(query)
|
||||||
|
.limit(250)
|
||||||
|
.sort({_id: 1})
|
||||||
|
.select(fields)
|
||||||
|
.lean()
|
||||||
|
.exec();
|
||||||
|
|
||||||
|
if (users.length === 0) {
|
||||||
|
console.warn('All appropriate users found and modified.');
|
||||||
|
console.warn(`\n${count} users processed\n`);
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
query._id = {
|
||||||
|
$gt: users[users.length - 1],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
|
||||||
|
}
|
||||||
|
};
|
||||||
28431
package-lock.json
generated
28431
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
182
package.json
182
package.json
@@ -1,135 +1,132 @@
|
|||||||
{
|
{
|
||||||
"name": "habitica",
|
"name": "habitica",
|
||||||
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
|
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
|
||||||
"version": "4.29.8",
|
"version": "4.78.0",
|
||||||
"main": "./website/server/index.js",
|
"main": "./website/server/index.js",
|
||||||
"greenkeeper": {
|
|
||||||
"ignore": [
|
|
||||||
"mongoose"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@slack/client": "^3.8.1",
|
"@slack/client": "^3.8.1",
|
||||||
"accepts": "^1.3.2",
|
"accepts": "^1.3.5",
|
||||||
"amazon-payments": "^0.2.6",
|
"amazon-payments": "^0.2.7",
|
||||||
"amplitude": "^3.5.0",
|
"amplitude": "^3.5.0",
|
||||||
"apidoc": "^0.17.5",
|
"apidoc": "^0.17.5",
|
||||||
"autoprefixer": "^8.0.0",
|
"apn": "^2.2.0",
|
||||||
"aws-sdk": "^2.200.0",
|
"autoprefixer": "^8.5.0",
|
||||||
|
"aws-sdk": "^2.329.0",
|
||||||
"axios": "^0.18.0",
|
"axios": "^0.18.0",
|
||||||
"axios-progress-bar": "^1.1.8",
|
"axios-progress-bar": "^1.2.0",
|
||||||
"babel-core": "^6.0.0",
|
"babel-core": "^6.26.3",
|
||||||
"babel-eslint": "^8.2.2",
|
"babel-eslint": "^8.2.3",
|
||||||
"babel-loader": "^7.1.2",
|
"babel-loader": "^7.1.4",
|
||||||
"babel-plugin-syntax-async-functions": "^6.13.0",
|
"babel-plugin-syntax-async-functions": "^6.13.0",
|
||||||
"babel-plugin-syntax-dynamic-import": "^6.18.0",
|
"babel-plugin-syntax-dynamic-import": "^6.18.0",
|
||||||
"babel-plugin-transform-async-to-module-method": "^6.8.0",
|
"babel-plugin-transform-es2015-modules-commonjs": "^6.26.2",
|
||||||
"babel-plugin-transform-object-rest-spread": "^6.16.0",
|
"babel-plugin-transform-object-rest-spread": "^6.16.0",
|
||||||
"babel-plugin-transform-regenerator": "^6.16.1",
|
"babel-plugin-transform-regenerator": "^6.16.1",
|
||||||
"babel-polyfill": "^6.6.1",
|
"babel-polyfill": "^6.6.1",
|
||||||
"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",
|
||||||
"bcrypt": "^1.0.2",
|
"bcrypt": "^3.0.1",
|
||||||
"bluebird": "^3.3.5",
|
"body-parser": "^1.18.3",
|
||||||
"body-parser": "^1.15.0",
|
"bootstrap": "^4.1.1",
|
||||||
"bootstrap": "^4.0.0",
|
"bootstrap-vue": "^2.0.0-rc.9",
|
||||||
"bootstrap-vue": "^2.0.0-rc.1",
|
|
||||||
"compression": "^1.7.2",
|
"compression": "^1.7.2",
|
||||||
"cookie-session": "^1.2.0",
|
"cookie-session": "^1.2.0",
|
||||||
"coupon-code": "^0.4.5",
|
"coupon-code": "^0.4.5",
|
||||||
"cross-env": "^5.1.3",
|
"cross-env": "^5.2.0",
|
||||||
"css-loader": "^0.28.0",
|
"css-loader": "^0.28.11",
|
||||||
"csv-stringify": "^2.0.4",
|
"csv-stringify": "^4.3.1",
|
||||||
"cwait": "^1.1.1",
|
"cwait": "^1.1.1",
|
||||||
"domain-middleware": "~0.1.0",
|
"domain-middleware": "~0.1.0",
|
||||||
"express": "^4.16.2",
|
"express": "^4.16.3",
|
||||||
"express-basic-auth": "^1.1.4",
|
"express-basic-auth": "^1.1.5",
|
||||||
"express-validator": "^5.0.1",
|
"express-validator": "^5.2.0",
|
||||||
"extract-text-webpack-plugin": "^3.0.2",
|
"extract-text-webpack-plugin": "^3.0.2",
|
||||||
"glob": "^7.1.2",
|
"glob": "^7.1.2",
|
||||||
"got": "^8.2.0",
|
"got": "^9.0.0",
|
||||||
"gulp": "^4.0.0",
|
"gulp": "^4.0.0",
|
||||||
"gulp-babel": "^7.0.1",
|
"gulp-babel": "^7.0.1",
|
||||||
"gulp-imagemin": "^4.1.0",
|
"gulp-imagemin": "^5.0.3",
|
||||||
"gulp-nodemon": "^2.2.1",
|
"gulp-nodemon": "^2.4.1",
|
||||||
"gulp.spritesmith": "^6.9.0",
|
"gulp.spritesmith": "^6.9.0",
|
||||||
"habitica-markdown": "^1.3.0",
|
"habitica-markdown": "^1.3.0",
|
||||||
"hellojs": "^1.15.1",
|
"hellojs": "^1.15.1",
|
||||||
"html-webpack-plugin": "^2.8.1",
|
"html-webpack-plugin": "^3.2.0",
|
||||||
"image-size": "^0.6.2",
|
"image-size": "^0.6.2",
|
||||||
"in-app-purchase": "^1.1.6",
|
"in-app-purchase": "^1.10.2",
|
||||||
"intro.js": "^2.6.0",
|
"intro.js": "^2.9.3",
|
||||||
"jquery": ">=3.0.0",
|
"jquery": ">=3.0.0",
|
||||||
"js2xmlparser": "^3.0.0",
|
"js2xmlparser": "^3.0.0",
|
||||||
"lodash": "^4.17.4",
|
"lodash": "^4.17.10",
|
||||||
"merge-stream": "^1.0.0",
|
"merge-stream": "^1.0.0",
|
||||||
"method-override": "^2.3.5",
|
"method-override": "^3.0.0",
|
||||||
"moment": "^2.13.0",
|
"moment": "^2.22.1",
|
||||||
"moment-recur": "^1.0.7",
|
"moment-recur": "^1.0.7",
|
||||||
"mongoose": "^4.13.11",
|
"mongoose": "^5.3.4",
|
||||||
"morgan": "^1.7.0",
|
"morgan": "^1.7.0",
|
||||||
"nconf": "^0.10.0",
|
"nconf": "^0.10.0",
|
||||||
"node-gcm": "^0.14.4",
|
"node-gcm": "^1.0.2",
|
||||||
"node-rdkafka": "^2.2.3",
|
"node-sass": "^4.9.0",
|
||||||
"node-sass": "^4.5.0",
|
"nodemailer": "^4.6.4",
|
||||||
"nodemailer": "^4.5.0",
|
"ora": "^3.0.0",
|
||||||
"ora": "^2.0.0",
|
|
||||||
"pageres": "^4.1.1",
|
"pageres": "^4.1.1",
|
||||||
"passport": "^0.4.0",
|
"passport": "^0.4.0",
|
||||||
"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.8.1",
|
"paypal-rest-sdk": "^1.8.1",
|
||||||
"popper.js": "^1.13.0",
|
"popper.js": "^1.14.3",
|
||||||
"postcss-easy-import": "^3.0.0",
|
"postcss-easy-import": "^3.0.0",
|
||||||
"ps-tree": "^1.0.0",
|
"ps-tree": "^1.0.0",
|
||||||
"pug": "^2.0.0-rc.4",
|
"pug": "^2.0.3",
|
||||||
"push-notify": "git://github.com/habitrpg/push-notify.git#6bc2b5fdb1bdc9649b9ec1964d79ca50187fc8a9",
|
|
||||||
"pusher": "^1.3.0",
|
|
||||||
"rimraf": "^2.4.3",
|
"rimraf": "^2.4.3",
|
||||||
"sass-loader": "^6.0.2",
|
"sass-loader": "^7.0.3",
|
||||||
"shelljs": "^0.8.1",
|
"shelljs": "^0.8.2",
|
||||||
"stackimpact": "^1.2.1",
|
"short-uuid": "^3.0.0",
|
||||||
"stripe": "^5.5.0",
|
"smartbanner.js": "^1.9.1",
|
||||||
"superagent": "^3.4.3",
|
"stripe": "^5.9.0",
|
||||||
|
"superagent": "^4.0.0",
|
||||||
"svg-inline-loader": "^0.8.0",
|
"svg-inline-loader": "^0.8.0",
|
||||||
"svg-url-loader": "^2.0.2",
|
"svg-url-loader": "^2.3.2",
|
||||||
"svgo": "^1.0.4",
|
"svgo": "^1.0.5",
|
||||||
"svgo-loader": "^2.1.0",
|
"svgo-loader": "^2.1.0",
|
||||||
"universal-analytics": "^0.4.16",
|
"universal-analytics": "^0.4.17",
|
||||||
"url-loader": "^0.6.2",
|
"update": "^0.7.4",
|
||||||
|
"upgrade": "^1.1.0",
|
||||||
|
"url-loader": "^1.0.0",
|
||||||
"useragent": "^2.1.9",
|
"useragent": "^2.1.9",
|
||||||
"uuid": "^3.0.1",
|
"uuid": "^3.0.1",
|
||||||
"validator": "^9.4.1",
|
"validator": "^10.5.0",
|
||||||
"vinyl-buffer": "^1.0.1",
|
"vinyl-buffer": "^1.0.1",
|
||||||
"vue": "^2.5.2",
|
"vue": "^2.5.16",
|
||||||
"vue-loader": "^14.1.1",
|
"vue-loader": "^14.2.2",
|
||||||
"vue-mugen-scroll": "^0.2.1",
|
"vue-mugen-scroll": "^0.2.1",
|
||||||
"vue-router": "^3.0.0",
|
"vue-router": "^3.0.0",
|
||||||
"vue-style-loader": "^4.0.2",
|
"vue-style-loader": "^4.1.0",
|
||||||
"vue-template-compiler": "^2.5.2",
|
"vue-template-compiler": "^2.5.16",
|
||||||
"vuedraggable": "^2.15.0",
|
"vuedraggable": "^2.15.0",
|
||||||
"vuejs-datepicker": "git://github.com/habitrpg/vuejs-datepicker.git#5d237615463a84a23dd6f3f77c6ab577d68593ec",
|
"vuejs-datepicker": "git://github.com/habitrpg/vuejs-datepicker.git#5d237615463a84a23dd6f3f77c6ab577d68593ec",
|
||||||
"webpack": "^3.11.0",
|
"webpack": "^3.12.0",
|
||||||
"webpack-merge": "^4.0.0",
|
"webpack-merge": "^4.1.3",
|
||||||
"winston": "^2.1.0",
|
"winston": "^2.4.3",
|
||||||
"winston-loggly-bulk": "^2.0.2",
|
"winston-loggly-bulk": "^2.0.2",
|
||||||
"xml2js": "^0.4.4"
|
"xml2js": "^0.4.4"
|
||||||
},
|
},
|
||||||
"private": true,
|
"private": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^6.9.1",
|
"node": "^10",
|
||||||
"npm": "^5.0.0"
|
"npm": "^6"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"lint": "eslint --ext .js,.vue .",
|
"lint": "eslint --ext .js,.vue .",
|
||||||
"test": "npm run lint && gulp test && 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:unit": "gulp test:api:unit",
|
||||||
"test:api-v3:integration": "gulp test:api-v3:integration",
|
"test:api-v3:integration": "gulp test:api-v3:integration",
|
||||||
"test:api-v3:integration:separate-server": "NODE_ENV=test gulp test:api-v3:integration:separate-server",
|
"test:api-v3:integration:separate-server": "NODE_ENV=test gulp test:api-v3:integration:separate-server",
|
||||||
|
"test:api-v4:integration": "gulp test:api-v4:integration",
|
||||||
|
"test:api-v4:integration:separate-server": "NODE_ENV=test gulp test:api-v4:integration:separate-server",
|
||||||
"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",
|
||||||
@@ -147,48 +144,51 @@
|
|||||||
"apidoc": "gulp apidoc"
|
"apidoc": "gulp apidoc"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"babel-plugin-istanbul": "^4.0.0",
|
"@vue/test-utils": "^1.0.0-beta.19",
|
||||||
|
"babel-plugin-istanbul": "^4.1.6",
|
||||||
|
"babel-plugin-syntax-object-rest-spread": "^6.13.0",
|
||||||
"chai": "^4.1.2",
|
"chai": "^4.1.2",
|
||||||
"chai-as-promised": "^7.1.1",
|
"chai-as-promised": "^7.1.1",
|
||||||
"chalk": "^2.3.1",
|
"chalk": "^2.4.1",
|
||||||
"chromedriver": "^2.27.2",
|
"chromedriver": "^2.40.0",
|
||||||
"connect-history-api-fallback": "^1.1.0",
|
"connect-history-api-fallback": "^1.1.0",
|
||||||
"coveralls": "^3.0.0",
|
"coveralls": "^3.0.1",
|
||||||
"cross-spawn": "^6.0.4",
|
"cross-spawn": "^6.0.5",
|
||||||
"eslint": "^4.18.1",
|
"eslint": "^4.19.1",
|
||||||
"eslint-config-habitrpg": "^4.0.0",
|
"eslint-config-habitrpg": "^4.0.0",
|
||||||
"eslint-friendly-formatter": "^3.0.0",
|
"eslint-friendly-formatter": "^4.0.1",
|
||||||
"eslint-loader": "^1.3.0",
|
"eslint-loader": "^2.0.0",
|
||||||
"eslint-plugin-html": "^4.0.2",
|
"eslint-plugin-html": "^4.0.3",
|
||||||
"eslint-plugin-mocha": "^4.7.0",
|
"eslint-plugin-mocha": "^5.0.0",
|
||||||
"eventsource-polyfill": "^0.9.6",
|
"eventsource-polyfill": "^0.9.6",
|
||||||
"expect.js": "^0.3.1",
|
"expect.js": "^0.3.1",
|
||||||
"http-proxy-middleware": "^0.17.0",
|
"http-proxy-middleware": "^0.19.0",
|
||||||
"istanbul": "^1.1.0-alpha.1",
|
"istanbul": "^1.1.0-alpha.1",
|
||||||
"karma": "^2.0.0",
|
"karma": "^3.1.3",
|
||||||
"karma-babel-preprocessor": "^7.0.0",
|
"karma-babel-preprocessor": "^7.0.0",
|
||||||
"karma-chai-plugins": "^0.9.0",
|
"karma-chai-plugins": "^0.9.0",
|
||||||
"karma-chrome-launcher": "^2.2.0",
|
"karma-chrome-launcher": "^2.2.0",
|
||||||
"karma-coverage": "^1.1.1",
|
"karma-coverage": "^1.1.2",
|
||||||
"karma-mocha": "^1.3.0",
|
"karma-mocha": "^1.3.0",
|
||||||
"karma-mocha-reporter": "^2.2.5",
|
"karma-mocha-reporter": "^2.2.5",
|
||||||
"karma-sinon-chai": "^1.3.3",
|
"karma-sinon-chai": "^2.0.0",
|
||||||
"karma-sinon-stub-promise": "^1.0.0",
|
"karma-sinon-stub-promise": "^1.0.0",
|
||||||
"karma-sourcemap-loader": "^0.3.7",
|
"karma-sourcemap-loader": "^0.3.7",
|
||||||
"karma-spec-reporter": "0.0.32",
|
"karma-spec-reporter": "0.0.32",
|
||||||
"karma-webpack": "^2.0.2",
|
"karma-webpack": "^3.0.0",
|
||||||
"lcov-result-merger": "^2.0.0",
|
"lcov-result-merger": "^3.0.0",
|
||||||
"mocha": "^5.0.1",
|
"mocha": "^5.1.1",
|
||||||
"monk": "^6.0.5",
|
"monk": "^6.0.6",
|
||||||
"nightwatch": "^0.9.12",
|
"nightwatch": "^0.9.21",
|
||||||
"puppeteer": "^1.1.0",
|
"puppeteer": "^1.5.0",
|
||||||
"require-again": "^2.0.0",
|
"require-again": "^2.0.0",
|
||||||
"selenium-server": "^3.9.1",
|
"selenium-server": "^3.12.0",
|
||||||
"sinon": "^4.3.0",
|
"sinon": "^6.3.5",
|
||||||
"sinon-chai": "^2.8.0",
|
"sinon-chai": "^3.0.0",
|
||||||
"sinon-stub-promise": "^4.0.0",
|
"sinon-stub-promise": "^4.0.0",
|
||||||
"webpack-bundle-analyzer": "^2.2.1",
|
"webpack-bundle-analyzer": "^2.12.0",
|
||||||
"webpack-dev-middleware": "^2.0.5",
|
"webpack-dev-middleware": "^2.0.5",
|
||||||
"webpack-hot-middleware": "^2.6.1"
|
"webpack-hot-middleware": "^2.22.2"
|
||||||
}
|
},
|
||||||
|
"optionalDependencies": {}
|
||||||
}
|
}
|
||||||
|
|||||||
89
scripts/gdpr-delete-users.js
Normal file
89
scripts/gdpr-delete-users.js
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
/* eslint-disable no-console */
|
||||||
|
import axios from 'axios';
|
||||||
|
import { model as User } from '../website/server/models/user';
|
||||||
|
import nconf from 'nconf';
|
||||||
|
|
||||||
|
const AMPLITUDE_KEY = nconf.get('AMPLITUDE_KEY');
|
||||||
|
const AMPLITUDE_SECRET = nconf.get('AMPLITUDE_SECRET');
|
||||||
|
const BASE_URL = nconf.get('BASE_URL');
|
||||||
|
|
||||||
|
async function _deleteAmplitudeData (userId, email) {
|
||||||
|
const response = await axios.post(
|
||||||
|
'https://amplitude.com/api/2/deletions/users',
|
||||||
|
{
|
||||||
|
user_ids: userId, // eslint-disable-line camelcase
|
||||||
|
requester: email,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
auth: {
|
||||||
|
username: AMPLITUDE_KEY,
|
||||||
|
password: AMPLITUDE_SECRET,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
).catch((err) => {
|
||||||
|
console.log(err.response.data);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response) console.log(`${response.status} ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function _deleteHabiticaData (user, email) {
|
||||||
|
await User.update(
|
||||||
|
{_id: user._id},
|
||||||
|
{$set: {
|
||||||
|
'auth.local.email': email,
|
||||||
|
'auth.local.hashed_password': '$2a$10$QDnNh1j1yMPnTXDEOV38xOePEWFd4X8DSYwAM8XTmqmacG5X0DKjW',
|
||||||
|
'auth.local.passwordHashMethod': 'bcrypt',
|
||||||
|
}}
|
||||||
|
);
|
||||||
|
const response = await axios.delete(
|
||||||
|
`${BASE_URL}/api/v3/user`,
|
||||||
|
{
|
||||||
|
data: {
|
||||||
|
password: 'test',
|
||||||
|
},
|
||||||
|
headers: {
|
||||||
|
'x-api-user': user._id,
|
||||||
|
'x-api-key': user.apiToken,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
).catch((err) => {
|
||||||
|
console.log(err.response.data);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response) {
|
||||||
|
console.log(`${response.status} ${response.statusText}`);
|
||||||
|
if (response.status === 200) console.log(`${user._id} removed. Last login: ${user.auth.timestamps.loggedin}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function _processEmailAddress (email) {
|
||||||
|
const emailRegex = new RegExp(`^${email}`, 'i');
|
||||||
|
const users = await User.find({
|
||||||
|
$or: [
|
||||||
|
{'auth.local.email': emailRegex},
|
||||||
|
{'auth.facebook.emails.value': emailRegex},
|
||||||
|
{'auth.google.emails.value': emailRegex},
|
||||||
|
]},
|
||||||
|
{
|
||||||
|
_id: 1,
|
||||||
|
apiToken: 1,
|
||||||
|
auth: 1,
|
||||||
|
}).exec();
|
||||||
|
|
||||||
|
if (users.length < 1) {
|
||||||
|
console.log(`No users found with email address ${email}`);
|
||||||
|
} else {
|
||||||
|
for (const user of users) {
|
||||||
|
await _deleteAmplitudeData(user._id, email); // eslint-disable-line no-await-in-loop
|
||||||
|
await _deleteHabiticaData(user, email); // eslint-disable-line no-await-in-loop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteUserData (emails) {
|
||||||
|
const emailPromises = emails.map(_processEmailAddress);
|
||||||
|
return Promise.all(emailPromises);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = deleteUserData;
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
require('babel-register');
|
require('babel-register');
|
||||||
require('babel-polyfill');
|
|
||||||
|
|
||||||
// This file is used for creating paypal billing plans. PayPal doesn't have a web interface for setting up recurring
|
// This file is used for creating paypal billing plans. PayPal doesn't have a web interface for setting up recurring
|
||||||
// payment plan definitions, instead you have to create it via their REST SDK and keep it updated the same way. So this
|
// payment plan definitions, instead you have to create it via their REST SDK and keep it updated the same way. So this
|
||||||
@@ -13,28 +12,27 @@ const nconf = require('nconf');
|
|||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const paypal = require('paypal-rest-sdk');
|
const paypal = require('paypal-rest-sdk');
|
||||||
const blocks = require('../website/common').content.subscriptionBlocks;
|
const blocks = require('../website/common').content.subscriptionBlocks;
|
||||||
const live = nconf.get('PAYPAL:mode') === 'live';
|
const live = nconf.get('PAYPAL_MODE') === 'live';
|
||||||
|
|
||||||
nconf.argv().env().file('user', path.join(path.resolve(__dirname, '../config.json')));
|
nconf.argv().env().file('user', path.join(path.resolve(__dirname, '../config.json')));
|
||||||
|
|
||||||
let OP = 'create'; // list create update remove
|
let OP = 'create'; // list get update create create-webprofile
|
||||||
|
|
||||||
paypal.configure({
|
paypal.configure({
|
||||||
mode: nconf.get('PAYPAL:mode'), // sandbox or live
|
mode: nconf.get('PAYPAL_MODE'), // sandbox or live
|
||||||
client_id: nconf.get('PAYPAL:client_id'),
|
client_id: nconf.get('PAYPAL_CLIENT_ID'),
|
||||||
client_secret: nconf.get('PAYPAL:client_secret'),
|
client_secret: nconf.get('PAYPAL_CLIENT_SECRET'),
|
||||||
});
|
});
|
||||||
|
|
||||||
// https://developer.paypal.com/docs/api/#billing-plans-and-agreements
|
// https://developer.paypal.com/docs/api/#billing-plans-and-agreements
|
||||||
let billingPlanTitle = 'Habitica Subscription';
|
let billingPlanTitle = 'Habitica Subscription';
|
||||||
let billingPlanAttributes = {
|
let billingPlanAttributes = {
|
||||||
name: billingPlanTitle,
|
|
||||||
description: billingPlanTitle,
|
description: billingPlanTitle,
|
||||||
type: 'INFINITE',
|
type: 'INFINITE',
|
||||||
merchant_preferences: {
|
merchant_preferences: {
|
||||||
auto_bill_amount: 'yes',
|
auto_bill_amount: 'yes',
|
||||||
cancel_url: live ? 'https://habitica.com' : 'http://localhost:3000',
|
cancel_url: live ? 'https://habitica.com' : 'http://localhost:3000',
|
||||||
return_url: `${live ? 'https://habitica.com' : 'http://localhost:3000' }/paypal/subscribe/success`,
|
return_url: `${live ? 'https://habitica.com' : 'http://localhost:3000'}/paypal/subscribe/success`,
|
||||||
},
|
},
|
||||||
payment_definitions: [{
|
payment_definitions: [{
|
||||||
type: 'REGULAR',
|
type: 'REGULAR',
|
||||||
@@ -46,7 +44,7 @@ let billingPlanAttributes = {
|
|||||||
_.each(blocks, (block) => {
|
_.each(blocks, (block) => {
|
||||||
block.definition = _.cloneDeep(billingPlanAttributes);
|
block.definition = _.cloneDeep(billingPlanAttributes);
|
||||||
_.merge(block.definition.payment_definitions[0], {
|
_.merge(block.definition.payment_definitions[0], {
|
||||||
name: `${billingPlanTitle } ($${block.price} every ${block.months} months, recurring)`,
|
name: `${billingPlanTitle} ($${block.price} every ${block.months} months, recurring)`,
|
||||||
frequency_interval: `${block.months}`,
|
frequency_interval: `${block.months}`,
|
||||||
amount: {
|
amount: {
|
||||||
currency: 'USD',
|
currency: 'USD',
|
||||||
@@ -64,7 +62,7 @@ switch (OP) {
|
|||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case 'get':
|
case 'get':
|
||||||
paypal.billingPlan.get(nconf.get('PAYPAL:billing_plans:12'), (err, plan) => {
|
paypal.billingPlan.get(nconf.get('PAYPAL_BILLING_PLANS_basic_12mo'), (err, plan) => {
|
||||||
console.log({err, plan});
|
console.log({err, plan});
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
@@ -76,7 +74,7 @@ switch (OP) {
|
|||||||
cancel_url: 'https://habitica.com',
|
cancel_url: 'https://habitica.com',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
paypal.billingPlan.update(nconf.get('PAYPAL:billing_plans:12'), updatePayload, (err, res) => {
|
paypal.billingPlan.update(nconf.get('PAYPAL_BILLING_PLANS_basic_12mo'), updatePayload, (err, res) => {
|
||||||
console.log({err, plan: res});
|
console.log({err, plan: res});
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
@@ -102,9 +100,6 @@ switch (OP) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'remove': break;
|
|
||||||
|
|
||||||
case 'create-webprofile':
|
case 'create-webprofile':
|
||||||
let webexpinfo = {
|
let webexpinfo = {
|
||||||
name: 'HabiticaProfile',
|
name: 'HabiticaProfile',
|
||||||
@@ -117,4 +112,4 @@ switch (OP) {
|
|||||||
console.log(error, result);
|
console.log(error, result);
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/* eslint-disable camelcase */
|
/* eslint-disable camelcase */
|
||||||
import analyticsService from '../../../../../website/server/libs/analyticsService';
|
import analyticsService from '../../../../website/server/libs/analyticsService';
|
||||||
import Amplitude from 'amplitude';
|
import Amplitude from 'amplitude';
|
||||||
import { Visitor } from 'universal-analytics';
|
import { Visitor } from 'universal-analytics';
|
||||||
|
|
||||||
@@ -1,19 +1,19 @@
|
|||||||
import apiMessages from '../../../../../website/server/libs/apiMessages';
|
import apiError from '../../../../website/server/libs/apiError';
|
||||||
|
|
||||||
describe('API Messages', () => {
|
describe('API Messages', () => {
|
||||||
const message = 'Only public guilds support pagination.';
|
const message = 'Only public guilds support pagination.';
|
||||||
it('returns an API message', () => {
|
it('returns an API message', () => {
|
||||||
expect(apiMessages('guildsOnlyPaginate')).to.equal(message);
|
expect(apiError('guildsOnlyPaginate')).to.equal(message);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('throws if the API message does not exist', () => {
|
it('throws if the API message does not exist', () => {
|
||||||
expect(() => apiMessages('iDoNotExist')).to.throw;
|
expect(() => apiError('iDoNotExist')).to.throw;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('clones the passed variables', () => {
|
it('clones the passed variables', () => {
|
||||||
let vars = {a: 1};
|
let vars = {a: 1};
|
||||||
sandbox.stub(_, 'clone').returns({});
|
sandbox.stub(_, 'clone').returns({});
|
||||||
apiMessages('guildsOnlyPaginate', vars);
|
apiError('guildsOnlyPaginate', vars);
|
||||||
expect(_.clone).to.have.been.calledOnce;
|
expect(_.clone).to.have.been.calledOnce;
|
||||||
expect(_.clone).to.have.been.calledWith(vars);
|
expect(_.clone).to.have.been.calledWith(vars);
|
||||||
});
|
});
|
||||||
@@ -22,7 +22,7 @@ describe('API Messages', () => {
|
|||||||
let vars = {a: 1};
|
let vars = {a: 1};
|
||||||
let stub = sinon.stub().returns('string');
|
let stub = sinon.stub().returns('string');
|
||||||
sandbox.stub(_, 'template').returns(stub);
|
sandbox.stub(_, 'template').returns(stub);
|
||||||
apiMessages('guildsOnlyPaginate', vars);
|
apiError('guildsOnlyPaginate', vars);
|
||||||
expect(_.template).to.have.been.calledOnce;
|
expect(_.template).to.have.been.calledOnce;
|
||||||
expect(_.template).to.have.been.calledWith(message);
|
expect(_.template).to.have.been.calledWith(message);
|
||||||
expect(stub).to.have.been.calledOnce;
|
expect(stub).to.have.been.calledOnce;
|
||||||
@@ -1,14 +1,21 @@
|
|||||||
import baseModel from '../../../../../website/server/libs/baseModel';
|
import baseModel from '../../../../website/server/libs/baseModel';
|
||||||
import mongoose from 'mongoose';
|
import mongoose from 'mongoose';
|
||||||
|
|
||||||
describe('Base model plugin', () => {
|
describe('Base model plugin', () => {
|
||||||
let schema;
|
let schema;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
schema = new mongoose.Schema();
|
schema = new mongoose.Schema({}, {
|
||||||
|
typeKey: '$type',
|
||||||
|
});
|
||||||
sandbox.stub(schema, 'add');
|
sandbox.stub(schema, 'add');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('throws if "typeKey" is not set to $type', () => {
|
||||||
|
const schemaWithoutTypeKey = new mongoose.Schema();
|
||||||
|
expect(() => schemaWithoutTypeKey.plugin(baseModel)).to.throw;
|
||||||
|
});
|
||||||
|
|
||||||
it('adds a _id field to the schema', () => {
|
it('adds a _id field to the schema', () => {
|
||||||
schema.plugin(baseModel);
|
schema.plugin(baseModel);
|
||||||
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import mongoose from 'mongoose';
|
import mongoose from 'mongoose';
|
||||||
import {
|
import {
|
||||||
removeFromArray,
|
removeFromArray,
|
||||||
} from '../../../../../website/server/libs/collectionManipulators';
|
} from '../../../../website/server/libs/collectionManipulators';
|
||||||
|
|
||||||
describe('Collection Manipulators', () => {
|
describe('Collection Manipulators', () => {
|
||||||
describe('removeFromArray', () => {
|
describe('removeFromArray', () => {
|
||||||
@@ -1,19 +1,19 @@
|
|||||||
/* eslint-disable global-require */
|
/* eslint-disable global-require */
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import nconf from 'nconf';
|
import nconf from 'nconf';
|
||||||
import Bluebird from 'bluebird';
|
|
||||||
import requireAgain from 'require-again';
|
import requireAgain from 'require-again';
|
||||||
import { recoverCron, cron } from '../../../../../website/server/libs/cron';
|
import { recoverCron, cron } from '../../../../website/server/libs/cron';
|
||||||
import { model as User } from '../../../../../website/server/models/user';
|
import { model as User } from '../../../../website/server/models/user';
|
||||||
import * as Tasks from '../../../../../website/server/models/task';
|
import * as Tasks from '../../../../website/server/models/task';
|
||||||
import common from '../../../../../website/common';
|
import common from '../../../../website/common';
|
||||||
import analytics from '../../../../../website/server/libs/analyticsService';
|
import analytics from '../../../../website/server/libs/analyticsService';
|
||||||
|
|
||||||
// const scoreTask = common.ops.scoreTask;
|
// const scoreTask = common.ops.scoreTask;
|
||||||
|
|
||||||
let pathToCronLib = '../../../../../website/server/libs/cron';
|
let pathToCronLib = '../../../../website/server/libs/cron';
|
||||||
|
|
||||||
describe('cron', () => {
|
describe('cron', () => {
|
||||||
|
let clock = null;
|
||||||
let user;
|
let user;
|
||||||
let tasksByType = {habits: [], dailys: [], todos: [], rewards: []};
|
let tasksByType = {habits: [], dailys: [], todos: [], rewards: []};
|
||||||
let daysMissed = 0;
|
let daysMissed = 0;
|
||||||
@@ -24,7 +24,7 @@ describe('cron', () => {
|
|||||||
local: {
|
local: {
|
||||||
username: 'username',
|
username: 'username',
|
||||||
lowerCaseUsername: 'username',
|
lowerCaseUsername: 'username',
|
||||||
email: 'email@email.email',
|
email: 'email@example.com',
|
||||||
salt: 'salt',
|
salt: 'salt',
|
||||||
hashed_password: 'hashed_password', // eslint-disable-line camelcase
|
hashed_password: 'hashed_password', // eslint-disable-line camelcase
|
||||||
},
|
},
|
||||||
@@ -35,6 +35,8 @@ describe('cron', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
if (clock !== null)
|
||||||
|
clock.restore();
|
||||||
analytics.track.restore();
|
analytics.track.restore();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -63,6 +65,12 @@ describe('cron', () => {
|
|||||||
expect(analytics.track.callCount).to.equal(1);
|
expect(analytics.track.callCount).to.equal(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('calls analytics when user is sleeping', () => {
|
||||||
|
user.preferences.sleep = true;
|
||||||
|
cron({user, tasksByType, daysMissed, analytics});
|
||||||
|
expect(analytics.track.callCount).to.equal(1);
|
||||||
|
});
|
||||||
|
|
||||||
describe('end of the month perks', () => {
|
describe('end of the month perks', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
user.purchased.plan.customerId = 'subscribedId';
|
user.purchased.plan.customerId = 'subscribedId';
|
||||||
@@ -83,14 +91,12 @@ describe('cron', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('does not reset plan.gemsBought within the month', () => {
|
it('does not reset plan.gemsBought within the month', () => {
|
||||||
let clock = sinon.useFakeTimers(moment().startOf('month').add(2, 'days').unix());
|
clock = sinon.useFakeTimers(moment().startOf('month').add(2, 'days').toDate());
|
||||||
user.purchased.plan.dateUpdated = moment().startOf('month').toDate();
|
user.purchased.plan.dateUpdated = moment().startOf('month').toDate();
|
||||||
|
|
||||||
user.purchased.plan.gemsBought = 10;
|
user.purchased.plan.gemsBought = 10;
|
||||||
cron({user, tasksByType, daysMissed, analytics});
|
cron({user, tasksByType, daysMissed, analytics});
|
||||||
expect(user.purchased.plan.gemsBought).to.equal(10);
|
expect(user.purchased.plan.gemsBought).to.equal(10);
|
||||||
|
|
||||||
clock.restore();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('resets plan.dateUpdated on a new month', () => {
|
it('resets plan.dateUpdated on a new month', () => {
|
||||||
@@ -118,21 +124,6 @@ describe('cron', () => {
|
|||||||
expect(user.purchased.plan.consecutive.offset).to.equal(1);
|
expect(user.purchased.plan.consecutive.offset).to.equal(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('increments plan.consecutive.trinkets when user has reached a month that is a multiple of 3', () => {
|
|
||||||
user.purchased.plan.consecutive.count = 5;
|
|
||||||
user.purchased.plan.consecutive.offset = 1;
|
|
||||||
cron({user, tasksByType, daysMissed, analytics});
|
|
||||||
expect(user.purchased.plan.consecutive.trinkets).to.equal(1);
|
|
||||||
expect(user.purchased.plan.consecutive.offset).to.equal(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('increments plan.consecutive.trinkets multiple times if user has been absent with continuous subscription', () => {
|
|
||||||
user.purchased.plan.dateUpdated = moment().subtract(6, 'months').toDate();
|
|
||||||
user.purchased.plan.consecutive.count = 5;
|
|
||||||
cron({user, tasksByType, daysMissed, analytics});
|
|
||||||
expect(user.purchased.plan.consecutive.trinkets).to.equal(2);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('does not award unearned plan.consecutive.trinkets if subscription ended during an absence', () => {
|
it('does not award unearned plan.consecutive.trinkets if subscription ended during an absence', () => {
|
||||||
user.purchased.plan.dateUpdated = moment().subtract(6, 'months').toDate();
|
user.purchased.plan.dateUpdated = moment().subtract(6, 'months').toDate();
|
||||||
user.purchased.plan.dateTerminated = moment().subtract(3, 'months').toDate();
|
user.purchased.plan.dateTerminated = moment().subtract(3, 'months').toDate();
|
||||||
@@ -144,21 +135,6 @@ describe('cron', () => {
|
|||||||
expect(user.purchased.plan.consecutive.trinkets).to.equal(1);
|
expect(user.purchased.plan.consecutive.trinkets).to.equal(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('increments plan.consecutive.gemCapExtra when user has reached a month that is a multiple of 3', () => {
|
|
||||||
user.purchased.plan.consecutive.count = 5;
|
|
||||||
user.purchased.plan.consecutive.offset = 1;
|
|
||||||
cron({user, tasksByType, daysMissed, analytics});
|
|
||||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.equal(5);
|
|
||||||
expect(user.purchased.plan.consecutive.offset).to.equal(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('increments plan.consecutive.gemCapExtra multiple times if user has been absent with continuous subscription', () => {
|
|
||||||
user.purchased.plan.dateUpdated = moment().subtract(6, 'months').toDate();
|
|
||||||
user.purchased.plan.consecutive.count = 5;
|
|
||||||
cron({user, tasksByType, daysMissed, analytics});
|
|
||||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.equal(10);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('does not increment plan.consecutive.gemCapExtra when user has reached the gemCap limit', () => {
|
it('does not increment plan.consecutive.gemCapExtra when user has reached the gemCap limit', () => {
|
||||||
user.purchased.plan.consecutive.gemCapExtra = 25;
|
user.purchased.plan.consecutive.gemCapExtra = 25;
|
||||||
user.purchased.plan.consecutive.count = 5;
|
user.purchased.plan.consecutive.count = 5;
|
||||||
@@ -185,6 +161,427 @@ describe('cron', () => {
|
|||||||
expect(user.purchased.plan.consecutive.count).to.equal(0);
|
expect(user.purchased.plan.consecutive.count).to.equal(0);
|
||||||
expect(user.purchased.plan.consecutive.offset).to.equal(0);
|
expect(user.purchased.plan.consecutive.offset).to.equal(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('for a 1-month recurring subscription', () => {
|
||||||
|
// create a user that will be used for all of these tests without a reset before each
|
||||||
|
let user1 = new User({
|
||||||
|
auth: {
|
||||||
|
local: {
|
||||||
|
username: 'username1',
|
||||||
|
lowerCaseUsername: 'username1',
|
||||||
|
email: 'email1@example.com',
|
||||||
|
salt: 'salt',
|
||||||
|
hashed_password: 'hashed_password', // eslint-disable-line camelcase
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
// user1 has a 1-month recurring subscription starting today
|
||||||
|
user1.purchased.plan.customerId = 'subscribedId';
|
||||||
|
user1.purchased.plan.dateUpdated = moment().toDate();
|
||||||
|
user1.purchased.plan.planId = 'basic';
|
||||||
|
user1.purchased.plan.consecutive.count = 0;
|
||||||
|
user1.purchased.plan.consecutive.offset = 0;
|
||||||
|
user1.purchased.plan.consecutive.trinkets = 0;
|
||||||
|
user1.purchased.plan.consecutive.gemCapExtra = 0;
|
||||||
|
|
||||||
|
it('does not increment consecutive benefits after the first month', () => {
|
||||||
|
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(1, 'months').add(2, 'days').toDate());
|
||||||
|
// Add 1 month to simulate what happens a month after the subscription was created.
|
||||||
|
// Add 2 days so that we're sure we're not affected by any start-of-month effects e.g., from time zone oddness.
|
||||||
|
cron({user: user1, tasksByType, daysMissed, analytics});
|
||||||
|
expect(user1.purchased.plan.consecutive.count).to.equal(1);
|
||||||
|
expect(user1.purchased.plan.consecutive.offset).to.equal(0);
|
||||||
|
expect(user1.purchased.plan.consecutive.trinkets).to.equal(0);
|
||||||
|
expect(user1.purchased.plan.consecutive.gemCapExtra).to.equal(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not increment consecutive benefits after the second month', () => {
|
||||||
|
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(2, 'months').add(2, 'days').toDate());
|
||||||
|
// Add 1 month to simulate what happens a month after the subscription was created.
|
||||||
|
// Add 2 days so that we're sure we're not affected by any start-of-month effects e.g., from time zone oddness.
|
||||||
|
cron({user: user1, tasksByType, daysMissed, analytics});
|
||||||
|
expect(user1.purchased.plan.consecutive.count).to.equal(2);
|
||||||
|
expect(user1.purchased.plan.consecutive.offset).to.equal(0);
|
||||||
|
expect(user1.purchased.plan.consecutive.trinkets).to.equal(0);
|
||||||
|
expect(user1.purchased.plan.consecutive.gemCapExtra).to.equal(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('increments consecutive benefits after the third month', () => {
|
||||||
|
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(3, 'months').add(2, 'days').toDate());
|
||||||
|
// Add 1 month to simulate what happens a month after the subscription was created.
|
||||||
|
// Add 2 days so that we're sure we're not affected by any start-of-month effects e.g., from time zone oddness.
|
||||||
|
cron({user: user1, tasksByType, daysMissed, analytics});
|
||||||
|
expect(user1.purchased.plan.consecutive.count).to.equal(3);
|
||||||
|
expect(user1.purchased.plan.consecutive.offset).to.equal(0);
|
||||||
|
expect(user1.purchased.plan.consecutive.trinkets).to.equal(1);
|
||||||
|
expect(user1.purchased.plan.consecutive.gemCapExtra).to.equal(5);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not increment consecutive benefits after the fourth month', () => {
|
||||||
|
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(4, 'months').add(2, 'days').toDate());
|
||||||
|
// Add 1 month to simulate what happens a month after the subscription was created.
|
||||||
|
// Add 2 days so that we're sure we're not affected by any start-of-month effects e.g., from time zone oddness.
|
||||||
|
cron({user: user1, tasksByType, daysMissed, analytics});
|
||||||
|
expect(user1.purchased.plan.consecutive.count).to.equal(4);
|
||||||
|
expect(user1.purchased.plan.consecutive.offset).to.equal(0);
|
||||||
|
expect(user1.purchased.plan.consecutive.trinkets).to.equal(1);
|
||||||
|
expect(user1.purchased.plan.consecutive.gemCapExtra).to.equal(5);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('increments consecutive benefits correctly if user has been absent with continuous subscription', () => {
|
||||||
|
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(10, 'months').add(2, 'days').toDate());
|
||||||
|
cron({user: user1, tasksByType, daysMissed, analytics});
|
||||||
|
expect(user1.purchased.plan.consecutive.count).to.equal(10);
|
||||||
|
expect(user1.purchased.plan.consecutive.offset).to.equal(0);
|
||||||
|
expect(user1.purchased.plan.consecutive.trinkets).to.equal(3);
|
||||||
|
expect(user1.purchased.plan.consecutive.gemCapExtra).to.equal(15);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('for a 3-month recurring subscription', () => {
|
||||||
|
let user3 = new User({
|
||||||
|
auth: {
|
||||||
|
local: {
|
||||||
|
username: 'username3',
|
||||||
|
lowerCaseUsername: 'username3',
|
||||||
|
email: 'email3@example.com',
|
||||||
|
salt: 'salt',
|
||||||
|
hashed_password: 'hashed_password', // eslint-disable-line camelcase
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
// user3 has a 3-month recurring subscription starting today
|
||||||
|
user3.purchased.plan.customerId = 'subscribedId';
|
||||||
|
user3.purchased.plan.dateUpdated = moment().toDate();
|
||||||
|
user3.purchased.plan.planId = 'basic_3mo';
|
||||||
|
user3.purchased.plan.consecutive.count = 0;
|
||||||
|
user3.purchased.plan.consecutive.offset = 3;
|
||||||
|
user3.purchased.plan.consecutive.trinkets = 1;
|
||||||
|
user3.purchased.plan.consecutive.gemCapExtra = 5;
|
||||||
|
|
||||||
|
it('does not increment consecutive benefits in the first month of the first paid period that they already have benefits for', () => {
|
||||||
|
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(1, 'months').add(2, 'days').toDate());
|
||||||
|
cron({user: user3, tasksByType, daysMissed, analytics});
|
||||||
|
expect(user3.purchased.plan.consecutive.count).to.equal(1);
|
||||||
|
expect(user3.purchased.plan.consecutive.offset).to.equal(2);
|
||||||
|
expect(user3.purchased.plan.consecutive.trinkets).to.equal(1);
|
||||||
|
expect(user3.purchased.plan.consecutive.gemCapExtra).to.equal(5);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not increment consecutive benefits in the middle of the period that they already have benefits for', () => {
|
||||||
|
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(2, 'months').add(2, 'days').toDate());
|
||||||
|
cron({user: user3, tasksByType, daysMissed, analytics});
|
||||||
|
expect(user3.purchased.plan.consecutive.count).to.equal(2);
|
||||||
|
expect(user3.purchased.plan.consecutive.offset).to.equal(1);
|
||||||
|
expect(user3.purchased.plan.consecutive.trinkets).to.equal(1);
|
||||||
|
expect(user3.purchased.plan.consecutive.gemCapExtra).to.equal(5);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not increment consecutive benefits in the final month of the period that they already have benefits for', () => {
|
||||||
|
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(3, 'months').add(2, 'days').toDate());
|
||||||
|
cron({user: user3, tasksByType, daysMissed, analytics});
|
||||||
|
expect(user3.purchased.plan.consecutive.count).to.equal(3);
|
||||||
|
expect(user3.purchased.plan.consecutive.offset).to.equal(0);
|
||||||
|
expect(user3.purchased.plan.consecutive.trinkets).to.equal(1);
|
||||||
|
expect(user3.purchased.plan.consecutive.gemCapExtra).to.equal(5);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('increments consecutive benefits the month after the second paid period has started', () => {
|
||||||
|
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(4, 'months').add(2, 'days').toDate());
|
||||||
|
cron({user: user3, tasksByType, daysMissed, analytics});
|
||||||
|
expect(user3.purchased.plan.consecutive.count).to.equal(4);
|
||||||
|
expect(user3.purchased.plan.consecutive.offset).to.equal(2);
|
||||||
|
expect(user3.purchased.plan.consecutive.trinkets).to.equal(2);
|
||||||
|
expect(user3.purchased.plan.consecutive.gemCapExtra).to.equal(10);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not increment consecutive benefits in the second month of the second period that they already have benefits for', () => {
|
||||||
|
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(5, 'months').add(2, 'days').toDate());
|
||||||
|
cron({user: user3, tasksByType, daysMissed, analytics});
|
||||||
|
expect(user3.purchased.plan.consecutive.count).to.equal(5);
|
||||||
|
expect(user3.purchased.plan.consecutive.offset).to.equal(1);
|
||||||
|
expect(user3.purchased.plan.consecutive.trinkets).to.equal(2);
|
||||||
|
expect(user3.purchased.plan.consecutive.gemCapExtra).to.equal(10);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not increment consecutive benefits in the final month of the second period that they already have benefits for', () => {
|
||||||
|
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(6, 'months').add(2, 'days').toDate());
|
||||||
|
cron({user: user3, tasksByType, daysMissed, analytics});
|
||||||
|
expect(user3.purchased.plan.consecutive.count).to.equal(6);
|
||||||
|
expect(user3.purchased.plan.consecutive.offset).to.equal(0);
|
||||||
|
expect(user3.purchased.plan.consecutive.trinkets).to.equal(2);
|
||||||
|
expect(user3.purchased.plan.consecutive.gemCapExtra).to.equal(10);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('increments consecutive benefits the month after the third paid period has started', () => {
|
||||||
|
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(7, 'months').add(2, 'days').toDate());
|
||||||
|
cron({user: user3, tasksByType, daysMissed, analytics});
|
||||||
|
expect(user3.purchased.plan.consecutive.count).to.equal(7);
|
||||||
|
expect(user3.purchased.plan.consecutive.offset).to.equal(2);
|
||||||
|
expect(user3.purchased.plan.consecutive.trinkets).to.equal(3);
|
||||||
|
expect(user3.purchased.plan.consecutive.gemCapExtra).to.equal(15);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('increments consecutive benefits correctly if user has been absent with continuous subscription', () => {
|
||||||
|
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(10, 'months').add(2, 'days').toDate());
|
||||||
|
cron({user: user3, tasksByType, daysMissed, analytics});
|
||||||
|
expect(user3.purchased.plan.consecutive.count).to.equal(10);
|
||||||
|
expect(user3.purchased.plan.consecutive.offset).to.equal(2);
|
||||||
|
expect(user3.purchased.plan.consecutive.trinkets).to.equal(4);
|
||||||
|
expect(user3.purchased.plan.consecutive.gemCapExtra).to.equal(20);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('for a 6-month recurring subscription', () => {
|
||||||
|
let user6 = new User({
|
||||||
|
auth: {
|
||||||
|
local: {
|
||||||
|
username: 'username6',
|
||||||
|
lowerCaseUsername: 'username6',
|
||||||
|
email: 'email6@example.com',
|
||||||
|
salt: 'salt',
|
||||||
|
hashed_password: 'hashed_password', // eslint-disable-line camelcase
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
// user6 has a 6-month recurring subscription starting today
|
||||||
|
user6.purchased.plan.customerId = 'subscribedId';
|
||||||
|
user6.purchased.plan.dateUpdated = moment().toDate();
|
||||||
|
user6.purchased.plan.planId = 'google_6mo';
|
||||||
|
user6.purchased.plan.consecutive.count = 0;
|
||||||
|
user6.purchased.plan.consecutive.offset = 6;
|
||||||
|
user6.purchased.plan.consecutive.trinkets = 2;
|
||||||
|
user6.purchased.plan.consecutive.gemCapExtra = 10;
|
||||||
|
|
||||||
|
it('does not increment consecutive benefits in the first month of the first paid period that they already have benefits for', () => {
|
||||||
|
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(1, 'months').add(2, 'days').toDate());
|
||||||
|
cron({user: user6, tasksByType, daysMissed, analytics});
|
||||||
|
expect(user6.purchased.plan.consecutive.count).to.equal(1);
|
||||||
|
expect(user6.purchased.plan.consecutive.offset).to.equal(5);
|
||||||
|
expect(user6.purchased.plan.consecutive.trinkets).to.equal(2);
|
||||||
|
expect(user6.purchased.plan.consecutive.gemCapExtra).to.equal(10);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not increment consecutive benefits in the final month of the period that they already have benefits for', () => {
|
||||||
|
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(6, 'months').add(2, 'days').toDate());
|
||||||
|
cron({user: user6, tasksByType, daysMissed, analytics});
|
||||||
|
expect(user6.purchased.plan.consecutive.count).to.equal(6);
|
||||||
|
expect(user6.purchased.plan.consecutive.offset).to.equal(0);
|
||||||
|
expect(user6.purchased.plan.consecutive.trinkets).to.equal(2);
|
||||||
|
expect(user6.purchased.plan.consecutive.gemCapExtra).to.equal(10);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('increments consecutive benefits the month after the second paid period has started', () => {
|
||||||
|
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(7, 'months').add(2, 'days').toDate());
|
||||||
|
cron({user: user6, tasksByType, daysMissed, analytics});
|
||||||
|
expect(user6.purchased.plan.consecutive.count).to.equal(7);
|
||||||
|
expect(user6.purchased.plan.consecutive.offset).to.equal(5);
|
||||||
|
expect(user6.purchased.plan.consecutive.trinkets).to.equal(4);
|
||||||
|
expect(user6.purchased.plan.consecutive.gemCapExtra).to.equal(20);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('increments consecutive benefits the month after the third paid period has started', () => {
|
||||||
|
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(13, 'months').add(2, 'days').toDate());
|
||||||
|
cron({user: user6, tasksByType, daysMissed, analytics});
|
||||||
|
expect(user6.purchased.plan.consecutive.count).to.equal(13);
|
||||||
|
expect(user6.purchased.plan.consecutive.offset).to.equal(5);
|
||||||
|
expect(user6.purchased.plan.consecutive.trinkets).to.equal(6);
|
||||||
|
expect(user6.purchased.plan.consecutive.gemCapExtra).to.equal(25);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('increments consecutive benefits correctly if user has been absent with continuous subscription', () => {
|
||||||
|
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(19, 'months').add(2, 'days').toDate());
|
||||||
|
cron({user: user6, tasksByType, daysMissed, analytics});
|
||||||
|
expect(user6.purchased.plan.consecutive.count).to.equal(19);
|
||||||
|
expect(user6.purchased.plan.consecutive.offset).to.equal(5);
|
||||||
|
expect(user6.purchased.plan.consecutive.trinkets).to.equal(8);
|
||||||
|
expect(user6.purchased.plan.consecutive.gemCapExtra).to.equal(25);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('for a 12-month recurring subscription', () => {
|
||||||
|
let user12 = new User({
|
||||||
|
auth: {
|
||||||
|
local: {
|
||||||
|
username: 'username12',
|
||||||
|
lowerCaseUsername: 'username12',
|
||||||
|
email: 'email12@example.com',
|
||||||
|
salt: 'salt',
|
||||||
|
hashed_password: 'hashed_password', // eslint-disable-line camelcase
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
// user12 has a 12-month recurring subscription starting today
|
||||||
|
user12.purchased.plan.customerId = 'subscribedId';
|
||||||
|
user12.purchased.plan.dateUpdated = moment().toDate();
|
||||||
|
user12.purchased.plan.planId = 'basic_12mo';
|
||||||
|
user12.purchased.plan.consecutive.count = 0;
|
||||||
|
user12.purchased.plan.consecutive.offset = 12;
|
||||||
|
user12.purchased.plan.consecutive.trinkets = 4;
|
||||||
|
user12.purchased.plan.consecutive.gemCapExtra = 20;
|
||||||
|
|
||||||
|
it('does not increment consecutive benefits in the first month of the first paid period that they already have benefits for', () => {
|
||||||
|
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(1, 'months').add(2, 'days').toDate());
|
||||||
|
cron({user: user12, tasksByType, daysMissed, analytics});
|
||||||
|
expect(user12.purchased.plan.consecutive.count).to.equal(1);
|
||||||
|
expect(user12.purchased.plan.consecutive.offset).to.equal(11);
|
||||||
|
expect(user12.purchased.plan.consecutive.trinkets).to.equal(4);
|
||||||
|
expect(user12.purchased.plan.consecutive.gemCapExtra).to.equal(20);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not increment consecutive benefits in the final month of the period that they already have benefits for', () => {
|
||||||
|
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(12, 'months').add(2, 'days').toDate());
|
||||||
|
cron({user: user12, tasksByType, daysMissed, analytics});
|
||||||
|
expect(user12.purchased.plan.consecutive.count).to.equal(12);
|
||||||
|
expect(user12.purchased.plan.consecutive.offset).to.equal(0);
|
||||||
|
expect(user12.purchased.plan.consecutive.trinkets).to.equal(4);
|
||||||
|
expect(user12.purchased.plan.consecutive.gemCapExtra).to.equal(20);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('increments consecutive benefits the month after the second paid period has started', () => {
|
||||||
|
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(13, 'months').add(2, 'days').toDate());
|
||||||
|
cron({user: user12, tasksByType, daysMissed, analytics});
|
||||||
|
expect(user12.purchased.plan.consecutive.count).to.equal(13);
|
||||||
|
expect(user12.purchased.plan.consecutive.offset).to.equal(11);
|
||||||
|
expect(user12.purchased.plan.consecutive.trinkets).to.equal(8);
|
||||||
|
expect(user12.purchased.plan.consecutive.gemCapExtra).to.equal(25);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('increments consecutive benefits the month after the third paid period has started', () => {
|
||||||
|
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(25, 'months').add(2, 'days').toDate());
|
||||||
|
cron({user: user12, tasksByType, daysMissed, analytics});
|
||||||
|
expect(user12.purchased.plan.consecutive.count).to.equal(25);
|
||||||
|
expect(user12.purchased.plan.consecutive.offset).to.equal(11);
|
||||||
|
expect(user12.purchased.plan.consecutive.trinkets).to.equal(12);
|
||||||
|
expect(user12.purchased.plan.consecutive.gemCapExtra).to.equal(25);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('increments consecutive benefits correctly if user has been absent with continuous subscription', () => {
|
||||||
|
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(37, 'months').add(2, 'days').toDate());
|
||||||
|
cron({user: user12, tasksByType, daysMissed, analytics});
|
||||||
|
expect(user12.purchased.plan.consecutive.count).to.equal(37);
|
||||||
|
expect(user12.purchased.plan.consecutive.offset).to.equal(11);
|
||||||
|
expect(user12.purchased.plan.consecutive.trinkets).to.equal(16);
|
||||||
|
expect(user12.purchased.plan.consecutive.gemCapExtra).to.equal(25);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('for a 3-month gift subscription (non-recurring)', () => {
|
||||||
|
let user3g = new User({
|
||||||
|
auth: {
|
||||||
|
local: {
|
||||||
|
username: 'username3g',
|
||||||
|
lowerCaseUsername: 'username3g',
|
||||||
|
email: 'email3g@example.com',
|
||||||
|
salt: 'salt',
|
||||||
|
hashed_password: 'hashed_password', // eslint-disable-line camelcase
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
// user3g has a 3-month gift subscription starting today
|
||||||
|
user3g.purchased.plan.customerId = 'Gift';
|
||||||
|
user3g.purchased.plan.dateUpdated = moment().toDate();
|
||||||
|
user3g.purchased.plan.dateTerminated = moment().startOf('month').add(3, 'months').add(15, 'days').toDate();
|
||||||
|
user3g.purchased.plan.planId = null;
|
||||||
|
user3g.purchased.plan.consecutive.count = 0;
|
||||||
|
user3g.purchased.plan.consecutive.offset = 3;
|
||||||
|
user3g.purchased.plan.consecutive.trinkets = 1;
|
||||||
|
user3g.purchased.plan.consecutive.gemCapExtra = 5;
|
||||||
|
|
||||||
|
it('does not increment consecutive benefits in the first month of the gift subscription', () => {
|
||||||
|
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(1, 'months').add(2, 'days').toDate());
|
||||||
|
cron({user: user3g, tasksByType, daysMissed, analytics});
|
||||||
|
expect(user3g.purchased.plan.consecutive.count).to.equal(1);
|
||||||
|
expect(user3g.purchased.plan.consecutive.offset).to.equal(2);
|
||||||
|
expect(user3g.purchased.plan.consecutive.trinkets).to.equal(1);
|
||||||
|
expect(user3g.purchased.plan.consecutive.gemCapExtra).to.equal(5);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not increment consecutive benefits in the second month of the gift subscription', () => {
|
||||||
|
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(2, 'months').add(2, 'days').toDate());
|
||||||
|
cron({user: user3g, tasksByType, daysMissed, analytics});
|
||||||
|
expect(user3g.purchased.plan.consecutive.count).to.equal(2);
|
||||||
|
expect(user3g.purchased.plan.consecutive.offset).to.equal(1);
|
||||||
|
expect(user3g.purchased.plan.consecutive.trinkets).to.equal(1);
|
||||||
|
expect(user3g.purchased.plan.consecutive.gemCapExtra).to.equal(5);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not increment consecutive benefits in the third month of the gift subscription', () => {
|
||||||
|
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(3, 'months').add(2, 'days').toDate());
|
||||||
|
cron({user: user3g, tasksByType, daysMissed, analytics});
|
||||||
|
expect(user3g.purchased.plan.consecutive.count).to.equal(3);
|
||||||
|
expect(user3g.purchased.plan.consecutive.offset).to.equal(0);
|
||||||
|
expect(user3g.purchased.plan.consecutive.trinkets).to.equal(1);
|
||||||
|
expect(user3g.purchased.plan.consecutive.gemCapExtra).to.equal(5);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not increment consecutive benefits in the month after the gift subscription has ended', () => {
|
||||||
|
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(4, 'months').add(2, 'days').toDate());
|
||||||
|
cron({user: user3g, tasksByType, daysMissed, analytics});
|
||||||
|
expect(user3g.purchased.plan.consecutive.count).to.equal(0); // subscription has been erased by now
|
||||||
|
expect(user3g.purchased.plan.consecutive.offset).to.equal(0);
|
||||||
|
expect(user3g.purchased.plan.consecutive.trinkets).to.equal(1);
|
||||||
|
expect(user3g.purchased.plan.consecutive.gemCapExtra).to.equal(0); // erased
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('for a 6-month recurring subscription where the user has incorrect consecutive month data from prior bugs', () => {
|
||||||
|
let user6x = new User({
|
||||||
|
auth: {
|
||||||
|
local: {
|
||||||
|
username: 'username6x',
|
||||||
|
lowerCaseUsername: 'username6x',
|
||||||
|
email: 'email6x@example.com',
|
||||||
|
salt: 'salt',
|
||||||
|
hashed_password: 'hashed_password', // eslint-disable-line camelcase
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
// user6x has a 6-month recurring subscription starting 8 months in the past before issue #4819 was fixed
|
||||||
|
user6x.purchased.plan.customerId = 'subscribedId';
|
||||||
|
user6x.purchased.plan.dateUpdated = moment().toDate();
|
||||||
|
user6x.purchased.plan.planId = 'basic_6mo';
|
||||||
|
user6x.purchased.plan.consecutive.count = 8;
|
||||||
|
user6x.purchased.plan.consecutive.offset = 0;
|
||||||
|
user6x.purchased.plan.consecutive.trinkets = 3;
|
||||||
|
user6x.purchased.plan.consecutive.gemCapExtra = 15;
|
||||||
|
|
||||||
|
it('increments consecutive benefits in the first month since the fix for #4819 goes live', () => {
|
||||||
|
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(1, 'months').add(2, 'days').toDate());
|
||||||
|
cron({user: user6x, tasksByType, daysMissed, analytics});
|
||||||
|
expect(user6x.purchased.plan.consecutive.count).to.equal(9);
|
||||||
|
expect(user6x.purchased.plan.consecutive.offset).to.equal(5);
|
||||||
|
expect(user6x.purchased.plan.consecutive.trinkets).to.equal(5);
|
||||||
|
expect(user6x.purchased.plan.consecutive.gemCapExtra).to.equal(25);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not increment consecutive benefits in the second month after the fix goes live', () => {
|
||||||
|
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(2, 'months').add(2, 'days').toDate());
|
||||||
|
cron({user: user6x, tasksByType, daysMissed, analytics});
|
||||||
|
expect(user6x.purchased.plan.consecutive.count).to.equal(10);
|
||||||
|
expect(user6x.purchased.plan.consecutive.offset).to.equal(4);
|
||||||
|
expect(user6x.purchased.plan.consecutive.trinkets).to.equal(5);
|
||||||
|
expect(user6x.purchased.plan.consecutive.gemCapExtra).to.equal(25);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not increment consecutive benefits in the third month after the fix goes live', () => {
|
||||||
|
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(3, 'months').add(2, 'days').toDate());
|
||||||
|
cron({user: user6x, tasksByType, daysMissed, analytics});
|
||||||
|
expect(user6x.purchased.plan.consecutive.count).to.equal(11);
|
||||||
|
expect(user6x.purchased.plan.consecutive.offset).to.equal(3);
|
||||||
|
expect(user6x.purchased.plan.consecutive.trinkets).to.equal(5);
|
||||||
|
expect(user6x.purchased.plan.consecutive.gemCapExtra).to.equal(25);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('increments consecutive benefits in the seventh month after the fix goes live', () => {
|
||||||
|
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(7, 'months').add(2, 'days').toDate());
|
||||||
|
cron({user: user6x, tasksByType, daysMissed, analytics});
|
||||||
|
expect(user6x.purchased.plan.consecutive.count).to.equal(15);
|
||||||
|
expect(user6x.purchased.plan.consecutive.offset).to.equal(5);
|
||||||
|
expect(user6x.purchased.plan.consecutive.trinkets).to.equal(7);
|
||||||
|
expect(user6x.purchased.plan.consecutive.gemCapExtra).to.equal(25);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('end of the month perks when user is not subscribed', () => {
|
describe('end of the month perks when user is not subscribed', () => {
|
||||||
@@ -199,14 +596,12 @@ describe('cron', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('does not reset plan.gemsBought within the month', () => {
|
it('does not reset plan.gemsBought within the month', () => {
|
||||||
let clock = sinon.useFakeTimers(moment().startOf('month').add(2, 'days').unix());
|
clock = sinon.useFakeTimers(moment().startOf('month').add(2, 'days').unix());
|
||||||
user.purchased.plan.dateUpdated = moment().startOf('month').toDate();
|
user.purchased.plan.dateUpdated = moment().startOf('month').toDate();
|
||||||
|
|
||||||
user.purchased.plan.gemsBought = 10;
|
user.purchased.plan.gemsBought = 10;
|
||||||
cron({user, tasksByType, daysMissed, analytics});
|
cron({user, tasksByType, daysMissed, analytics});
|
||||||
expect(user.purchased.plan.gemsBought).to.equal(10);
|
expect(user.purchased.plan.gemsBought).to.equal(10);
|
||||||
|
|
||||||
clock.restore();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not reset plan.dateUpdated on a new month', () => {
|
it('does not reset plan.dateUpdated on a new month', () => {
|
||||||
@@ -266,76 +661,6 @@ describe('cron', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('user is sleeping', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
user.preferences.sleep = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('calls analytics', () => {
|
|
||||||
cron({user, tasksByType, daysMissed, analytics});
|
|
||||||
expect(analytics.track.callCount).to.equal(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('clears user buffs', () => {
|
|
||||||
user.stats.buffs = {
|
|
||||||
str: 1,
|
|
||||||
int: 1,
|
|
||||||
per: 1,
|
|
||||||
con: 1,
|
|
||||||
stealth: 1,
|
|
||||||
streaks: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
cron({user, tasksByType, daysMissed, analytics});
|
|
||||||
|
|
||||||
expect(user.stats.buffs.str).to.equal(0);
|
|
||||||
expect(user.stats.buffs.int).to.equal(0);
|
|
||||||
expect(user.stats.buffs.per).to.equal(0);
|
|
||||||
expect(user.stats.buffs.con).to.equal(0);
|
|
||||||
expect(user.stats.buffs.stealth).to.equal(0);
|
|
||||||
expect(user.stats.buffs.streaks).to.be.false;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('resets all dailies without damaging user', () => {
|
|
||||||
let daily = {
|
|
||||||
text: 'test daily',
|
|
||||||
type: 'daily',
|
|
||||||
frequency: 'daily',
|
|
||||||
everyX: 5,
|
|
||||||
startDate: new Date(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let task = new Tasks.daily(Tasks.Task.sanitize(daily)); // eslint-disable-line new-cap
|
|
||||||
tasksByType.dailys.push(task);
|
|
||||||
tasksByType.dailys[0].completed = true;
|
|
||||||
|
|
||||||
let healthBefore = user.stats.hp;
|
|
||||||
|
|
||||||
cron({user, tasksByType, daysMissed, analytics});
|
|
||||||
|
|
||||||
expect(tasksByType.dailys[0].completed).to.be.false;
|
|
||||||
expect(user.stats.hp).to.equal(healthBefore);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('sets isDue for daily', () => {
|
|
||||||
let daily = {
|
|
||||||
text: 'test daily',
|
|
||||||
type: 'daily',
|
|
||||||
frequency: 'daily',
|
|
||||||
everyX: 5,
|
|
||||||
startDate: new Date(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let task = new Tasks.daily(Tasks.Task.sanitize(daily)); // eslint-disable-line new-cap
|
|
||||||
tasksByType.dailys.push(task);
|
|
||||||
tasksByType.dailys[0].completed = true;
|
|
||||||
|
|
||||||
cron({user, tasksByType, daysMissed, analytics});
|
|
||||||
|
|
||||||
expect(tasksByType.dailys[0].isDue).to.be.exist;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('todos', () => {
|
describe('todos', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
let todo = {
|
let todo = {
|
||||||
@@ -457,6 +782,15 @@ describe('cron', () => {
|
|||||||
expect(tasksByType.dailys[0].isDue).to.be.false;
|
expect(tasksByType.dailys[0].isDue).to.be.false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('computes isDue when user is sleeping', () => {
|
||||||
|
user.preferences.sleep = true;
|
||||||
|
tasksByType.dailys[0].frequency = 'daily';
|
||||||
|
tasksByType.dailys[0].everyX = 5;
|
||||||
|
tasksByType.dailys[0].startDate = moment().toDate();
|
||||||
|
cron({user, tasksByType, daysMissed, analytics});
|
||||||
|
expect(tasksByType.dailys[0].isDue).to.exist;
|
||||||
|
});
|
||||||
|
|
||||||
it('computes nextDue', () => {
|
it('computes nextDue', () => {
|
||||||
tasksByType.dailys[0].frequency = 'daily';
|
tasksByType.dailys[0].frequency = 'daily';
|
||||||
tasksByType.dailys[0].everyX = 5;
|
tasksByType.dailys[0].everyX = 5;
|
||||||
@@ -476,6 +810,13 @@ describe('cron', () => {
|
|||||||
expect(tasksByType.dailys[0].completed).to.be.false;
|
expect(tasksByType.dailys[0].completed).to.be.false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should set tasks completed to false when user is sleeping', () => {
|
||||||
|
user.preferences.sleep = true;
|
||||||
|
tasksByType.dailys[0].completed = true;
|
||||||
|
cron({user, tasksByType, daysMissed, analytics});
|
||||||
|
expect(tasksByType.dailys[0].completed).to.be.false;
|
||||||
|
});
|
||||||
|
|
||||||
it('should reset task checklist for completed dailys', () => {
|
it('should reset task checklist for completed dailys', () => {
|
||||||
tasksByType.dailys[0].checklist.push({title: 'test', completed: false});
|
tasksByType.dailys[0].checklist.push({title: 'test', completed: false});
|
||||||
tasksByType.dailys[0].completed = true;
|
tasksByType.dailys[0].completed = true;
|
||||||
@@ -483,6 +824,14 @@ describe('cron', () => {
|
|||||||
expect(tasksByType.dailys[0].checklist[0].completed).to.be.false;
|
expect(tasksByType.dailys[0].checklist[0].completed).to.be.false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should reset task checklist for completed dailys when user is sleeping', () => {
|
||||||
|
user.preferences.sleep = true;
|
||||||
|
tasksByType.dailys[0].checklist.push({title: 'test', completed: false});
|
||||||
|
tasksByType.dailys[0].completed = true;
|
||||||
|
cron({user, tasksByType, daysMissed, analytics});
|
||||||
|
expect(tasksByType.dailys[0].checklist[0].completed).to.be.false;
|
||||||
|
});
|
||||||
|
|
||||||
it('should reset task checklist for dailys with scheduled misses', () => {
|
it('should reset task checklist for dailys with scheduled misses', () => {
|
||||||
daysMissed = 10;
|
daysMissed = 10;
|
||||||
tasksByType.dailys[0].checklist.push({title: 'test', completed: false});
|
tasksByType.dailys[0].checklist.push({title: 'test', completed: false});
|
||||||
@@ -495,12 +844,19 @@ describe('cron', () => {
|
|||||||
daysMissed = 1;
|
daysMissed = 1;
|
||||||
let hpBefore = user.stats.hp;
|
let hpBefore = user.stats.hp;
|
||||||
tasksByType.dailys[0].startDate = moment(new Date()).subtract({days: 1});
|
tasksByType.dailys[0].startDate = moment(new Date()).subtract({days: 1});
|
||||||
|
|
||||||
cron({user, tasksByType, daysMissed, analytics});
|
cron({user, tasksByType, daysMissed, analytics});
|
||||||
|
|
||||||
expect(user.stats.hp).to.be.lessThan(hpBefore);
|
expect(user.stats.hp).to.be.lessThan(hpBefore);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should not do damage for missing a daily when user is sleeping', () => {
|
||||||
|
user.preferences.sleep = true;
|
||||||
|
daysMissed = 1;
|
||||||
|
let hpBefore = user.stats.hp;
|
||||||
|
tasksByType.dailys[0].startDate = moment(new Date()).subtract({days: 1});
|
||||||
|
cron({user, tasksByType, daysMissed, analytics});
|
||||||
|
expect(user.stats.hp).to.equal(hpBefore);
|
||||||
|
});
|
||||||
|
|
||||||
it('should not do damage for missing a daily when CRON_SAFE_MODE is set', () => {
|
it('should not do damage for missing a daily when CRON_SAFE_MODE is set', () => {
|
||||||
sandbox.stub(nconf, 'get').withArgs('CRON_SAFE_MODE').returns('true');
|
sandbox.stub(nconf, 'get').withArgs('CRON_SAFE_MODE').returns('true');
|
||||||
let cronOverride = requireAgain(pathToCronLib).cron;
|
let cronOverride = requireAgain(pathToCronLib).cron;
|
||||||
@@ -541,7 +897,7 @@ describe('cron', () => {
|
|||||||
expect(hpDifferenceOfPartiallyIncompleteDaily).to.be.lessThan(hpDifferenceOfFullyIncompleteDaily);
|
expect(hpDifferenceOfPartiallyIncompleteDaily).to.be.lessThan(hpDifferenceOfFullyIncompleteDaily);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should decrement quest progress down for missing a daily', () => {
|
it('should decrement quest.progress.down for missing a daily', () => {
|
||||||
daysMissed = 1;
|
daysMissed = 1;
|
||||||
tasksByType.dailys[0].startDate = moment(new Date()).subtract({days: 1});
|
tasksByType.dailys[0].startDate = moment(new Date()).subtract({days: 1});
|
||||||
|
|
||||||
@@ -550,6 +906,16 @@ describe('cron', () => {
|
|||||||
expect(progress.down).to.equal(-1);
|
expect(progress.down).to.equal(-1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should not decrement quest.progress.down for missing a daily when user is sleeping', () => {
|
||||||
|
user.preferences.sleep = true;
|
||||||
|
daysMissed = 1;
|
||||||
|
tasksByType.dailys[0].startDate = moment(new Date()).subtract({days: 1});
|
||||||
|
|
||||||
|
let progress = cron({user, tasksByType, daysMissed, analytics});
|
||||||
|
|
||||||
|
expect(progress.down).to.equal(0);
|
||||||
|
});
|
||||||
|
|
||||||
it('should do damage for only yesterday\'s dailies', () => {
|
it('should do damage for only yesterday\'s dailies', () => {
|
||||||
daysMissed = 3;
|
daysMissed = 3;
|
||||||
tasksByType.dailys[0].startDate = moment(new Date()).subtract({days: 1});
|
tasksByType.dailys[0].startDate = moment(new Date()).subtract({days: 1});
|
||||||
@@ -612,15 +978,11 @@ describe('cron', () => {
|
|||||||
|
|
||||||
describe('counters', () => {
|
describe('counters', () => {
|
||||||
let notStartOfWeekOrMonth = new Date(2016, 9, 28).getTime(); // a Friday
|
let notStartOfWeekOrMonth = new Date(2016, 9, 28).getTime(); // a Friday
|
||||||
let clock;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
// Replace system clocks so we can get predictable results
|
// Replace system clocks so we can get predictable results
|
||||||
clock = sinon.useFakeTimers(notStartOfWeekOrMonth);
|
clock = sinon.useFakeTimers(notStartOfWeekOrMonth);
|
||||||
});
|
});
|
||||||
afterEach(() => {
|
|
||||||
return clock.restore();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should reset a daily habit counter each day', () => {
|
it('should reset a daily habit counter each day', () => {
|
||||||
tasksByType.habits[0].counterUp = 1;
|
tasksByType.habits[0].counterUp = 1;
|
||||||
@@ -632,7 +994,7 @@ describe('cron', () => {
|
|||||||
expect(tasksByType.habits[0].counterDown).to.equal(0);
|
expect(tasksByType.habits[0].counterDown).to.equal(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should reset habit counters even if user is resting in the Inn', () => {
|
it('should reset habit counters even if user is sleeping', () => {
|
||||||
user.preferences.sleep = true;
|
user.preferences.sleep = true;
|
||||||
tasksByType.habits[0].counterUp = 1;
|
tasksByType.habits[0].counterUp = 1;
|
||||||
tasksByType.habits[0].counterDown = 1;
|
tasksByType.habits[0].counterDown = 1;
|
||||||
@@ -893,7 +1255,23 @@ describe('cron', () => {
|
|||||||
expect(user.achievements.perfect).to.equal(0);
|
expect(user.achievements.perfect).to.equal(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('increments user buffs if all (at least 1) due dailies were completed', () => {
|
it('gives perfect day buff if all (at least 1) due dailies were completed', () => {
|
||||||
|
daysMissed = 1;
|
||||||
|
tasksByType.dailys[0].completed = true;
|
||||||
|
tasksByType.dailys[0].startDate = moment(new Date()).subtract({days: 1});
|
||||||
|
|
||||||
|
let previousBuffs = user.stats.buffs.toObject();
|
||||||
|
|
||||||
|
cron({user, tasksByType, daysMissed, analytics});
|
||||||
|
|
||||||
|
expect(user.stats.buffs.str).to.be.greaterThan(previousBuffs.str);
|
||||||
|
expect(user.stats.buffs.int).to.be.greaterThan(previousBuffs.int);
|
||||||
|
expect(user.stats.buffs.per).to.be.greaterThan(previousBuffs.per);
|
||||||
|
expect(user.stats.buffs.con).to.be.greaterThan(previousBuffs.con);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('gives perfect day buff if all (at least 1) due dailies were completed when user is sleeping', () => {
|
||||||
|
user.preferences.sleep = true;
|
||||||
daysMissed = 1;
|
daysMissed = 1;
|
||||||
tasksByType.dailys[0].completed = true;
|
tasksByType.dailys[0].completed = true;
|
||||||
tasksByType.dailys[0].startDate = moment(new Date()).subtract({days: 1});
|
tasksByType.dailys[0].startDate = moment(new Date()).subtract({days: 1});
|
||||||
@@ -932,6 +1310,31 @@ describe('cron', () => {
|
|||||||
expect(user.stats.buffs.streaks).to.be.false;
|
expect(user.stats.buffs.streaks).to.be.false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('clears buffs if user does not have a perfect day (no due dailys) when user is sleeping', () => {
|
||||||
|
user.preferences.sleep = true;
|
||||||
|
daysMissed = 1;
|
||||||
|
tasksByType.dailys[0].completed = true;
|
||||||
|
tasksByType.dailys[0].startDate = moment(new Date()).add({days: 1});
|
||||||
|
|
||||||
|
user.stats.buffs = {
|
||||||
|
str: 1,
|
||||||
|
int: 1,
|
||||||
|
per: 1,
|
||||||
|
con: 1,
|
||||||
|
stealth: 0,
|
||||||
|
streaks: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
cron({user, tasksByType, daysMissed, analytics});
|
||||||
|
|
||||||
|
expect(user.stats.buffs.str).to.equal(0);
|
||||||
|
expect(user.stats.buffs.int).to.equal(0);
|
||||||
|
expect(user.stats.buffs.per).to.equal(0);
|
||||||
|
expect(user.stats.buffs.con).to.equal(0);
|
||||||
|
expect(user.stats.buffs.stealth).to.equal(0);
|
||||||
|
expect(user.stats.buffs.streaks).to.be.false;
|
||||||
|
});
|
||||||
|
|
||||||
it('clears buffs if user does not have a perfect day (at least one due daily not completed)', () => {
|
it('clears buffs if user does not have a perfect day (at least one due daily not completed)', () => {
|
||||||
daysMissed = 1;
|
daysMissed = 1;
|
||||||
tasksByType.dailys[0].completed = false;
|
tasksByType.dailys[0].completed = false;
|
||||||
@@ -956,7 +1359,50 @@ describe('cron', () => {
|
|||||||
expect(user.stats.buffs.streaks).to.be.false;
|
expect(user.stats.buffs.streaks).to.be.false;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('still grants a perfect day when CRON_SAFE_MODE is set', () => {
|
it('clears buffs if user does not have a perfect day (at least one due daily not completed) when user is sleeping', () => {
|
||||||
|
user.preferences.sleep = true;
|
||||||
|
daysMissed = 1;
|
||||||
|
tasksByType.dailys[0].completed = false;
|
||||||
|
tasksByType.dailys[0].startDate = moment(new Date()).subtract({days: 1});
|
||||||
|
|
||||||
|
user.stats.buffs = {
|
||||||
|
str: 1,
|
||||||
|
int: 1,
|
||||||
|
per: 1,
|
||||||
|
con: 1,
|
||||||
|
stealth: 0,
|
||||||
|
streaks: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
cron({user, tasksByType, daysMissed, analytics});
|
||||||
|
|
||||||
|
expect(user.stats.buffs.str).to.equal(0);
|
||||||
|
expect(user.stats.buffs.int).to.equal(0);
|
||||||
|
expect(user.stats.buffs.per).to.equal(0);
|
||||||
|
expect(user.stats.buffs.con).to.equal(0);
|
||||||
|
expect(user.stats.buffs.stealth).to.equal(0);
|
||||||
|
expect(user.stats.buffs.streaks).to.be.false;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('always grants a perfect day buff when CRON_SAFE_MODE is set', () => {
|
||||||
|
sandbox.stub(nconf, 'get').withArgs('CRON_SAFE_MODE').returns('true');
|
||||||
|
let cronOverride = requireAgain(pathToCronLib).cron;
|
||||||
|
daysMissed = 1;
|
||||||
|
tasksByType.dailys[0].completed = false;
|
||||||
|
tasksByType.dailys[0].startDate = moment(new Date()).subtract({days: 1});
|
||||||
|
|
||||||
|
let previousBuffs = user.stats.buffs.toObject();
|
||||||
|
|
||||||
|
cronOverride({user, tasksByType, daysMissed, analytics});
|
||||||
|
|
||||||
|
expect(user.stats.buffs.str).to.be.greaterThan(previousBuffs.str);
|
||||||
|
expect(user.stats.buffs.int).to.be.greaterThan(previousBuffs.int);
|
||||||
|
expect(user.stats.buffs.per).to.be.greaterThan(previousBuffs.per);
|
||||||
|
expect(user.stats.buffs.con).to.be.greaterThan(previousBuffs.con);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('always grants a perfect day buff when CRON_SAFE_MODE is set when user is sleeping', () => {
|
||||||
|
user.preferences.sleep = true;
|
||||||
sandbox.stub(nconf, 'get').withArgs('CRON_SAFE_MODE').returns('true');
|
sandbox.stub(nconf, 'get').withArgs('CRON_SAFE_MODE').returns('true');
|
||||||
let cronOverride = requireAgain(pathToCronLib).cron;
|
let cronOverride = requireAgain(pathToCronLib).cron;
|
||||||
daysMissed = 1;
|
daysMissed = 1;
|
||||||
@@ -988,6 +1434,20 @@ describe('cron', () => {
|
|||||||
common.statsComputed.restore();
|
common.statsComputed.restore();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should not add mp to user when user is sleeping', () => {
|
||||||
|
const statsComputedRes = common.statsComputed(user);
|
||||||
|
const stubbedStatsComputed = sinon.stub(common, 'statsComputed');
|
||||||
|
|
||||||
|
user.preferences.sleep = true;
|
||||||
|
let mpBefore = user.stats.mp;
|
||||||
|
tasksByType.dailys[0].completed = true;
|
||||||
|
stubbedStatsComputed.returns(Object.assign(statsComputedRes, {maxMP: 100}));
|
||||||
|
cron({user, tasksByType, daysMissed, analytics});
|
||||||
|
expect(user.stats.mp).to.equal(mpBefore);
|
||||||
|
|
||||||
|
common.statsComputed.restore();
|
||||||
|
});
|
||||||
|
|
||||||
it('set user\'s mp to statsComputed.maxMP when user.stats.mp is greater', () => {
|
it('set user\'s mp to statsComputed.maxMP when user.stats.mp is greater', () => {
|
||||||
const statsComputedRes = common.statsComputed(user);
|
const statsComputedRes = common.statsComputed(user);
|
||||||
const stubbedStatsComputed = sinon.stub(common, 'statsComputed');
|
const stubbedStatsComputed = sinon.stub(common, 'statsComputed');
|
||||||
@@ -1129,27 +1589,6 @@ describe('cron', () => {
|
|||||||
flagCount: 0,
|
flagCount: 0,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
xit('does not clear pms under 200', () => {
|
|
||||||
cron({user, tasksByType, daysMissed, analytics});
|
|
||||||
expect(user.inbox.messages[lastMessageId]).to.exist;
|
|
||||||
});
|
|
||||||
|
|
||||||
xit('clears pms over 200', () => {
|
|
||||||
let messageId = common.uuid();
|
|
||||||
user.inbox.messages[messageId] = {
|
|
||||||
id: messageId,
|
|
||||||
text: `test ${messageId}`,
|
|
||||||
timestamp: Number(new Date()),
|
|
||||||
likes: {},
|
|
||||||
flags: {},
|
|
||||||
flagCount: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
cron({user, tasksByType, daysMissed, analytics});
|
|
||||||
|
|
||||||
expect(user.inbox.messages[messageId]).to.not.exist;
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('login incentives', () => {
|
describe('login incentives', () => {
|
||||||
@@ -1183,7 +1622,7 @@ describe('cron', () => {
|
|||||||
expect(user.loginIncentives).to.eql(1);
|
expect(user.loginIncentives).to.eql(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('increments loginIncentives by 1 even if user has Dailies paused', () => {
|
it('increments loginIncentives by 1 even if user is sleeping', () => {
|
||||||
user.preferences.sleep = true;
|
user.preferences.sleep = true;
|
||||||
cron({user, tasksByType, daysMissed, analytics});
|
cron({user, tasksByType, daysMissed, analytics});
|
||||||
expect(user.loginIncentives).to.eql(1);
|
expect(user.loginIncentives).to.eql(1);
|
||||||
@@ -1349,7 +1788,7 @@ describe('recoverCron', () => {
|
|||||||
local: {
|
local: {
|
||||||
username: 'username',
|
username: 'username',
|
||||||
lowerCaseUsername: 'username',
|
lowerCaseUsername: 'username',
|
||||||
email: 'email@email.email',
|
email: 'email@example.com',
|
||||||
salt: 'salt',
|
salt: 'salt',
|
||||||
hashed_password: 'hashed_password', // eslint-disable-line camelcase
|
hashed_password: 'hashed_password', // eslint-disable-line camelcase
|
||||||
},
|
},
|
||||||
@@ -1363,7 +1802,7 @@ describe('recoverCron', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('throws an error if user cannot be found', async () => {
|
it('throws an error if user cannot be found', async () => {
|
||||||
execStub.returns(Bluebird.resolve(null));
|
execStub.returns(Promise.resolve(null));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await recoverCron(status, locals);
|
await recoverCron(status, locals);
|
||||||
@@ -1374,8 +1813,8 @@ describe('recoverCron', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('increases status.times count and reruns up to 4 times', async () => {
|
it('increases status.times count and reruns up to 4 times', async () => {
|
||||||
execStub.returns(Bluebird.resolve({_cronSignature: 'RUNNING_CRON'}));
|
execStub.returns(Promise.resolve({_cronSignature: 'RUNNING_CRON'}));
|
||||||
execStub.onCall(4).returns(Bluebird.resolve({_cronSignature: 'NOT_RUNNING'}));
|
execStub.onCall(4).returns(Promise.resolve({_cronSignature: 'NOT_RUNNING'}));
|
||||||
|
|
||||||
await recoverCron(status, locals);
|
await recoverCron(status, locals);
|
||||||
|
|
||||||
@@ -1384,7 +1823,7 @@ describe('recoverCron', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('throws an error if recoverCron runs 5 times', async () => {
|
it('throws an error if recoverCron runs 5 times', async () => {
|
||||||
execStub.returns(Bluebird.resolve({_cronSignature: 'RUNNING_CRON'}));
|
execStub.returns(Promise.resolve({_cronSignature: 'RUNNING_CRON'}));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await recoverCron(status, locals);
|
await recoverCron(status, locals);
|
||||||
@@ -3,9 +3,9 @@ import got from 'got';
|
|||||||
import nconf from 'nconf';
|
import nconf from 'nconf';
|
||||||
import nodemailer from 'nodemailer';
|
import nodemailer from 'nodemailer';
|
||||||
import requireAgain from 'require-again';
|
import requireAgain from 'require-again';
|
||||||
import logger from '../../../../../website/server/libs/logger';
|
import logger from '../../../../website/server/libs/logger';
|
||||||
import { TAVERN_ID } from '../../../../../website/server/models/group';
|
import { TAVERN_ID } from '../../../../website/server/models/group';
|
||||||
import { defer } from '../../../../helpers/api-unit.helper';
|
import { defer } from '../../../helpers/api-unit.helper';
|
||||||
|
|
||||||
function getUser () {
|
function getUser () {
|
||||||
return {
|
return {
|
||||||
@@ -19,7 +19,6 @@ function getUser () {
|
|||||||
emails: [{
|
emails: [{
|
||||||
value: 'email@facebook',
|
value: 'email@facebook',
|
||||||
}],
|
}],
|
||||||
displayName: 'fb display name',
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
profile: {
|
profile: {
|
||||||
@@ -34,7 +33,7 @@ function getUser () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
describe('emails', () => {
|
describe('emails', () => {
|
||||||
let pathToEmailLib = '../../../../../website/server/libs/email';
|
let pathToEmailLib = '../../../../website/server/libs/email';
|
||||||
|
|
||||||
describe('sendEmail', () => {
|
describe('sendEmail', () => {
|
||||||
let sendMailSpy;
|
let sendMailSpy;
|
||||||
@@ -100,7 +99,7 @@ describe('emails', () => {
|
|||||||
|
|
||||||
let data = getUserInfo(user, ['name', 'email', '_id', 'canSend']);
|
let data = getUserInfo(user, ['name', 'email', '_id', 'canSend']);
|
||||||
|
|
||||||
expect(data).to.have.property('name', user.auth.facebook.displayName);
|
expect(data).to.have.property('name', user.profile.name);
|
||||||
expect(data).to.have.property('email', user.auth.facebook.emails[0].value);
|
expect(data).to.have.property('email', user.auth.facebook.emails[0].value);
|
||||||
expect(data).to.have.property('_id', user._id);
|
expect(data).to.have.property('_id', user._id);
|
||||||
expect(data).to.have.property('canSend', true);
|
expect(data).to.have.property('canSend', true);
|
||||||
@@ -110,13 +109,12 @@ describe('emails', () => {
|
|||||||
let attachEmail = requireAgain(pathToEmailLib);
|
let attachEmail = requireAgain(pathToEmailLib);
|
||||||
let getUserInfo = attachEmail.getUserInfo;
|
let getUserInfo = attachEmail.getUserInfo;
|
||||||
let user = getUser();
|
let user = getUser();
|
||||||
delete user.profile.name;
|
|
||||||
delete user.auth.local.email;
|
delete user.auth.local.email;
|
||||||
delete user.auth.facebook;
|
delete user.auth.facebook;
|
||||||
|
|
||||||
let data = getUserInfo(user, ['name', 'email', '_id', 'canSend']);
|
let data = getUserInfo(user, ['name', 'email', '_id', 'canSend']);
|
||||||
|
|
||||||
expect(data).to.have.property('name', user.auth.local.username);
|
expect(data).to.have.property('name', user.profile.name);
|
||||||
expect(data).not.to.have.property('email');
|
expect(data).not.to.have.property('email');
|
||||||
expect(data).to.have.property('_id', user._id);
|
expect(data).to.have.property('_id', user._id);
|
||||||
expect(data).to.have.property('canSend', true);
|
expect(data).to.have.property('canSend', true);
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
encrypt,
|
encrypt,
|
||||||
decrypt,
|
decrypt,
|
||||||
} from '../../../../../website/server/libs/encryption';
|
} from '../../../../website/server/libs/encryption';
|
||||||
|
|
||||||
describe('encryption', () => {
|
describe('encryption', () => {
|
||||||
it('can encrypt and decrypt', () => {
|
it('can encrypt and decrypt', () => {
|
||||||
@@ -5,7 +5,7 @@ import {
|
|||||||
BadRequest,
|
BadRequest,
|
||||||
InternalServerError,
|
InternalServerError,
|
||||||
NotFound,
|
NotFound,
|
||||||
} from '../../../../../website/server/libs/errors';
|
} from '../../../../website/server/libs/errors';
|
||||||
|
|
||||||
describe('Custom Errors', () => {
|
describe('Custom Errors', () => {
|
||||||
describe('CustomError', () => {
|
describe('CustomError', () => {
|
||||||
@@ -2,7 +2,7 @@ import {
|
|||||||
translations,
|
translations,
|
||||||
localePath,
|
localePath,
|
||||||
langCodes,
|
langCodes,
|
||||||
} from '../../../../../website/server/libs/i18n';
|
} from '../../../../website/server/libs/i18n';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import winston from 'winston';
|
import winston from 'winston';
|
||||||
import logger from '../../../../../website/server/libs/logger';
|
import logger from '../../../../website/server/libs/logger';
|
||||||
import {
|
import {
|
||||||
NotFound,
|
NotFound,
|
||||||
} from '../../../../../website/server/libs//errors';
|
} from '../../../../website/server/libs//errors';
|
||||||
|
|
||||||
describe('logger', () => {
|
describe('logger', () => {
|
||||||
let logSpy;
|
let logSpy;
|
||||||
@@ -2,11 +2,11 @@
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
encrypt,
|
encrypt,
|
||||||
} from '../../../../../website/server/libs/encryption';
|
} from '../../../../website/server/libs/encryption';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import {
|
import {
|
||||||
generateUser,
|
generateUser,
|
||||||
} from '../../../../helpers/api-integration/v3';
|
} from '../../../helpers/api-integration/v3';
|
||||||
import {
|
import {
|
||||||
sha1Encrypt as sha1EncryptPassword,
|
sha1Encrypt as sha1EncryptPassword,
|
||||||
sha1MakeSalt,
|
sha1MakeSalt,
|
||||||
@@ -15,7 +15,7 @@ import {
|
|||||||
compare,
|
compare,
|
||||||
convertToBcrypt,
|
convertToBcrypt,
|
||||||
validatePasswordResetCodeAndFindUser,
|
validatePasswordResetCodeAndFindUser,
|
||||||
} from '../../../../../website/server/libs/password';
|
} from '../../../../website/server/libs/password';
|
||||||
|
|
||||||
describe('Password Utilities', () => {
|
describe('Password Utilities', () => {
|
||||||
describe('compare', () => {
|
describe('compare', () => {
|
||||||
@@ -107,6 +107,25 @@ describe('Password Utilities', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('defaults to SHA1 encryption if salt is provided', async () => {
|
||||||
|
let textPassword = 'mySecretPassword';
|
||||||
|
let salt = sha1MakeSalt();
|
||||||
|
let hashedPassword = sha1EncryptPassword(textPassword, salt);
|
||||||
|
|
||||||
|
let user = {
|
||||||
|
auth: {
|
||||||
|
local: {
|
||||||
|
hashed_password: hashedPassword,
|
||||||
|
salt,
|
||||||
|
passwordHashMethod: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let isValidPassword = await compare(user, textPassword);
|
||||||
|
expect(isValidPassword).to.eql(true);
|
||||||
|
});
|
||||||
|
|
||||||
it('throws an error if an invalid hashing method is used', async () => {
|
it('throws an error if an invalid hashing method is used', async () => {
|
||||||
try {
|
try {
|
||||||
await compare({
|
await compare({
|
||||||
@@ -226,7 +245,9 @@ describe('Password Utilities', () => {
|
|||||||
|
|
||||||
it('returns false if the user has no local auth', async () => {
|
it('returns false if the user has no local auth', async () => {
|
||||||
let user = await generateUser({
|
let user = await generateUser({
|
||||||
auth: 'not an object with valid fields',
|
auth: {
|
||||||
|
facebook: {},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
let res = await validatePasswordResetCodeAndFindUser(encrypt(JSON.stringify({
|
let res = await validatePasswordResetCodeAndFindUser(encrypt(JSON.stringify({
|
||||||
userId: user._id,
|
userId: user._id,
|
||||||
@@ -2,11 +2,11 @@ import moment from 'moment';
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
generateGroup,
|
generateGroup,
|
||||||
} from '../../../../../../helpers/api-unit.helper.js';
|
} from '../../../../../helpers/api-unit.helper.js';
|
||||||
import { model as User } from '../../../../../../../website/server/models/user';
|
import { model as User } from '../../../../../../website/server/models/user';
|
||||||
import amzLib from '../../../../../../../website/server/libs/amazonPayments';
|
import amzLib from '../../../../../../website/server/libs/payments/amazon';
|
||||||
import payments from '../../../../../../../website/server/libs/payments';
|
import payments from '../../../../../../website/server/libs/payments/payments';
|
||||||
import common from '../../../../../../../website/common';
|
import common from '../../../../../../website/common';
|
||||||
import { createNonLeaderGroupMember } from '../paymentHelpers';
|
import { createNonLeaderGroupMember } from '../paymentHelpers';
|
||||||
|
|
||||||
const i18n = common.i18n;
|
const i18n = common.i18n;
|
||||||
@@ -48,7 +48,6 @@ describe('Amazon Payments - Cancel Subscription', () => {
|
|||||||
|
|
||||||
function expectBillingAggreementDetailSpy () {
|
function expectBillingAggreementDetailSpy () {
|
||||||
getBillingAgreementDetailsSpy = sinon.stub(amzLib, 'getBillingAgreementDetails')
|
getBillingAgreementDetailsSpy = sinon.stub(amzLib, 'getBillingAgreementDetails')
|
||||||
.returnsPromise()
|
|
||||||
.resolves({
|
.resolves({
|
||||||
BillingAgreementDetails: {
|
BillingAgreementDetails: {
|
||||||
BillingAgreementStatus: {State: 'Open'},
|
BillingAgreementStatus: {State: 'Open'},
|
||||||
@@ -80,14 +79,14 @@ describe('Amazon Payments - Cancel Subscription', () => {
|
|||||||
headers = {};
|
headers = {};
|
||||||
|
|
||||||
getBillingAgreementDetailsSpy = sinon.stub(amzLib, 'getBillingAgreementDetails');
|
getBillingAgreementDetailsSpy = sinon.stub(amzLib, 'getBillingAgreementDetails');
|
||||||
getBillingAgreementDetailsSpy.returnsPromise().resolves({
|
getBillingAgreementDetailsSpy.resolves({
|
||||||
BillingAgreementDetails: {
|
BillingAgreementDetails: {
|
||||||
BillingAgreementStatus: {State: 'Closed'},
|
BillingAgreementStatus: {State: 'Closed'},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
paymentCancelSubscriptionSpy = sinon.stub(payments, 'cancelSubscription');
|
paymentCancelSubscriptionSpy = sinon.stub(payments, 'cancelSubscription');
|
||||||
paymentCancelSubscriptionSpy.returnsPromise().resolves({});
|
paymentCancelSubscriptionSpy.resolves({});
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(function () {
|
afterEach(function () {
|
||||||
@@ -118,7 +117,7 @@ describe('Amazon Payments - Cancel Subscription', () => {
|
|||||||
it('should close a user subscription if amazon not closed', async () => {
|
it('should close a user subscription if amazon not closed', async () => {
|
||||||
amzLib.getBillingAgreementDetails.restore();
|
amzLib.getBillingAgreementDetails.restore();
|
||||||
expectBillingAggreementDetailSpy();
|
expectBillingAggreementDetailSpy();
|
||||||
let closeBillingAgreementSpy = sinon.stub(amzLib, 'closeBillingAgreement').returnsPromise().resolves({});
|
let closeBillingAgreementSpy = sinon.stub(amzLib, 'closeBillingAgreement').resolves({});
|
||||||
billingAgreementId = user.purchased.plan.customerId;
|
billingAgreementId = user.purchased.plan.customerId;
|
||||||
|
|
||||||
await amzLib.cancelSubscription({user, headers});
|
await amzLib.cancelSubscription({user, headers});
|
||||||
@@ -164,7 +163,7 @@ describe('Amazon Payments - Cancel Subscription', () => {
|
|||||||
it('should close a group subscription if amazon not closed', async () => {
|
it('should close a group subscription if amazon not closed', async () => {
|
||||||
amzLib.getBillingAgreementDetails.restore();
|
amzLib.getBillingAgreementDetails.restore();
|
||||||
expectBillingAggreementDetailSpy();
|
expectBillingAggreementDetailSpy();
|
||||||
let closeBillingAgreementSpy = sinon.stub(amzLib, 'closeBillingAgreement').returnsPromise().resolves({});
|
let closeBillingAgreementSpy = sinon.stub(amzLib, 'closeBillingAgreement').resolves({});
|
||||||
billingAgreementId = group.purchased.plan.customerId;
|
billingAgreementId = group.purchased.plan.customerId;
|
||||||
|
|
||||||
await amzLib.cancelSubscription({user, groupId: group._id, headers});
|
await amzLib.cancelSubscription({user, groupId: group._id, headers});
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { model as User } from '../../../../../../../website/server/models/user';
|
import { model as User } from '../../../../../../website/server/models/user';
|
||||||
import amzLib from '../../../../../../../website/server/libs/amazonPayments';
|
import amzLib from '../../../../../../website/server/libs/payments/amazon';
|
||||||
import payments from '../../../../../../../website/server/libs/payments';
|
import payments from '../../../../../../website/server/libs/payments/payments';
|
||||||
import common from '../../../../../../../website/common';
|
import common from '../../../../../../website/common';
|
||||||
|
|
||||||
const i18n = common.i18n;
|
const i18n = common.i18n;
|
||||||
|
|
||||||
@@ -68,22 +68,22 @@ describe('Amazon Payments - Checkout', () => {
|
|||||||
orderReferenceId = 'orderReferenceId';
|
orderReferenceId = 'orderReferenceId';
|
||||||
|
|
||||||
setOrderReferenceDetailsSpy = sinon.stub(amzLib, 'setOrderReferenceDetails');
|
setOrderReferenceDetailsSpy = sinon.stub(amzLib, 'setOrderReferenceDetails');
|
||||||
setOrderReferenceDetailsSpy.returnsPromise().resolves({});
|
setOrderReferenceDetailsSpy.resolves({});
|
||||||
|
|
||||||
confirmOrderReferenceSpy = sinon.stub(amzLib, 'confirmOrderReference');
|
confirmOrderReferenceSpy = sinon.stub(amzLib, 'confirmOrderReference');
|
||||||
confirmOrderReferenceSpy.returnsPromise().resolves({});
|
confirmOrderReferenceSpy.resolves({});
|
||||||
|
|
||||||
authorizeSpy = sinon.stub(amzLib, 'authorize');
|
authorizeSpy = sinon.stub(amzLib, 'authorize');
|
||||||
authorizeSpy.returnsPromise().resolves({});
|
authorizeSpy.resolves({});
|
||||||
|
|
||||||
closeOrderReferenceSpy = sinon.stub(amzLib, 'closeOrderReference');
|
closeOrderReferenceSpy = sinon.stub(amzLib, 'closeOrderReference');
|
||||||
closeOrderReferenceSpy.returnsPromise().resolves({});
|
closeOrderReferenceSpy.resolves({});
|
||||||
|
|
||||||
paymentBuyGemsStub = sinon.stub(payments, 'buyGems');
|
paymentBuyGemsStub = sinon.stub(payments, 'buyGems');
|
||||||
paymentBuyGemsStub.returnsPromise().resolves({});
|
paymentBuyGemsStub.resolves({});
|
||||||
|
|
||||||
paymentCreateSubscritionStub = sinon.stub(payments, 'createSubscription');
|
paymentCreateSubscritionStub = sinon.stub(payments, 'createSubscription');
|
||||||
paymentCreateSubscritionStub.returnsPromise().resolves({});
|
paymentCreateSubscritionStub.resolves({});
|
||||||
|
|
||||||
sinon.stub(common, 'uuid').returns('uuid-generated');
|
sinon.stub(common, 'uuid').returns('uuid-generated');
|
||||||
});
|
});
|
||||||
@@ -111,7 +111,7 @@ describe('Amazon Payments - Checkout', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
it('should purchase gems', async () => {
|
it('should purchase gems', async () => {
|
||||||
sinon.stub(user, 'canGetGems').returnsPromise().resolves(true);
|
sinon.stub(user, 'canGetGems').resolves(true);
|
||||||
await amzLib.checkout({user, orderReferenceId, headers});
|
await amzLib.checkout({user, orderReferenceId, headers});
|
||||||
|
|
||||||
expectBuyGemsStub(amzLib.constants.PAYMENT_METHOD);
|
expectBuyGemsStub(amzLib.constants.PAYMENT_METHOD);
|
||||||
@@ -140,7 +140,7 @@ describe('Amazon Payments - Checkout', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should error if user cannot get gems gems', async () => {
|
it('should error if user cannot get gems gems', async () => {
|
||||||
sinon.stub(user, 'canGetGems').returnsPromise().resolves(false);
|
sinon.stub(user, 'canGetGems').resolves(false);
|
||||||
await expect(amzLib.checkout({user, orderReferenceId, headers})).to.eventually.be.rejected.and.to.eql({
|
await expect(amzLib.checkout({user, orderReferenceId, headers})).to.eventually.be.rejected.and.to.eql({
|
||||||
httpCode: 401,
|
httpCode: 401,
|
||||||
message: i18n.t('groupPolicyCannotGetGems'),
|
message: i18n.t('groupPolicyCannotGetGems'),
|
||||||
@@ -2,12 +2,12 @@ import cc from 'coupon-code';
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
generateGroup,
|
generateGroup,
|
||||||
} from '../../../../../../helpers/api-unit.helper.js';
|
} from '../../../../../helpers/api-unit.helper.js';
|
||||||
import { model as User } from '../../../../../../../website/server/models/user';
|
import { model as User } from '../../../../../../website/server/models/user';
|
||||||
import { model as Coupon } from '../../../../../../../website/server/models/coupon';
|
import { model as Coupon } from '../../../../../../website/server/models/coupon';
|
||||||
import amzLib from '../../../../../../../website/server/libs/amazonPayments';
|
import amzLib from '../../../../../../website/server/libs/payments/amazon';
|
||||||
import payments from '../../../../../../../website/server/libs/payments';
|
import payments from '../../../../../../website/server/libs/payments/payments';
|
||||||
import common from '../../../../../../../website/common';
|
import common from '../../../../../../website/common';
|
||||||
|
|
||||||
const i18n = common.i18n;
|
const i18n = common.i18n;
|
||||||
|
|
||||||
@@ -46,16 +46,16 @@ describe('Amazon Payments - Subscribe', () => {
|
|||||||
headers = {};
|
headers = {};
|
||||||
|
|
||||||
amazonSetBillingAgreementDetailsSpy = sinon.stub(amzLib, 'setBillingAgreementDetails');
|
amazonSetBillingAgreementDetailsSpy = sinon.stub(amzLib, 'setBillingAgreementDetails');
|
||||||
amazonSetBillingAgreementDetailsSpy.returnsPromise().resolves({});
|
amazonSetBillingAgreementDetailsSpy.resolves({});
|
||||||
|
|
||||||
amazonConfirmBillingAgreementSpy = sinon.stub(amzLib, 'confirmBillingAgreement');
|
amazonConfirmBillingAgreementSpy = sinon.stub(amzLib, 'confirmBillingAgreement');
|
||||||
amazonConfirmBillingAgreementSpy.returnsPromise().resolves({});
|
amazonConfirmBillingAgreementSpy.resolves({});
|
||||||
|
|
||||||
amazonAuthorizeOnBillingAgreementSpy = sinon.stub(amzLib, 'authorizeOnBillingAgreement');
|
amazonAuthorizeOnBillingAgreementSpy = sinon.stub(amzLib, 'authorizeOnBillingAgreement');
|
||||||
amazonAuthorizeOnBillingAgreementSpy.returnsPromise().resolves({});
|
amazonAuthorizeOnBillingAgreementSpy.resolves({});
|
||||||
|
|
||||||
createSubSpy = sinon.stub(payments, 'createSubscription');
|
createSubSpy = sinon.stub(payments, 'createSubscription');
|
||||||
createSubSpy.returnsPromise().resolves({});
|
createSubSpy.resolves({});
|
||||||
|
|
||||||
sinon.stub(common, 'uuid').returns('uuid-generated');
|
sinon.stub(common, 'uuid').returns('uuid-generated');
|
||||||
});
|
});
|
||||||
@@ -2,11 +2,11 @@ import uuid from 'uuid';
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
generateGroup,
|
generateGroup,
|
||||||
} from '../../../../../../helpers/api-unit.helper.js';
|
} from '../../../../../helpers/api-unit.helper.js';
|
||||||
import { model as User } from '../../../../../../../website/server/models/user';
|
import { model as User } from '../../../../../../website/server/models/user';
|
||||||
import { model as Group } from '../../../../../../../website/server/models/group';
|
import { model as Group } from '../../../../../../website/server/models/group';
|
||||||
import amzLib from '../../../../../../../website/server/libs/amazonPayments';
|
import amzLib from '../../../../../../website/server/libs/payments/amazon';
|
||||||
import payments from '../../../../../../../website/server/libs/payments';
|
import payments from '../../../../../../website/server/libs/payments/payments';
|
||||||
|
|
||||||
describe('#upgradeGroupPlan', () => {
|
describe('#upgradeGroupPlan', () => {
|
||||||
let spy, data, user, group, uuidString;
|
let spy, data, user, group, uuidString;
|
||||||
@@ -37,7 +37,7 @@ describe('#upgradeGroupPlan', () => {
|
|||||||
await group.save();
|
await group.save();
|
||||||
|
|
||||||
spy = sinon.stub(amzLib, 'authorizeOnBillingAgreement');
|
spy = sinon.stub(amzLib, 'authorizeOnBillingAgreement');
|
||||||
spy.returnsPromise().resolves([]);
|
spy.resolves([]);
|
||||||
|
|
||||||
uuidString = 'uuid-v4';
|
uuidString = 'uuid-v4';
|
||||||
sinon.stub(uuid, 'v4').returns(uuidString);
|
sinon.stub(uuid, 'v4').returns(uuidString);
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
/* eslint-disable camelcase */
|
/* eslint-disable camelcase */
|
||||||
import iapModule from '../../../../../website/server/libs/inAppPurchases';
|
import iapModule from '../../../../../website/server/libs/inAppPurchases';
|
||||||
import payments from '../../../../../website/server/libs/payments';
|
import payments from '../../../../../website/server/libs/payments/payments';
|
||||||
import applePayments from '../../../../../website/server/libs/applePayments';
|
import applePayments from '../../../../../website/server/libs/payments/apple';
|
||||||
import iap from '../../../../../website/server/libs/inAppPurchases';
|
import iap from '../../../../../website/server/libs/inAppPurchases';
|
||||||
import {model as User} from '../../../../../website/server/models/user';
|
import {model as User} from '../../../../../website/server/models/user';
|
||||||
import common from '../../../../../website/common';
|
import common from '../../../../../website/common';
|
||||||
@@ -24,16 +24,16 @@ describe('Apple Payments', () => {
|
|||||||
headers = {};
|
headers = {};
|
||||||
|
|
||||||
iapSetupStub = sinon.stub(iapModule, 'setup')
|
iapSetupStub = sinon.stub(iapModule, 'setup')
|
||||||
.returnsPromise().resolves();
|
.resolves();
|
||||||
iapValidateStub = sinon.stub(iapModule, 'validate')
|
iapValidateStub = sinon.stub(iapModule, 'validate')
|
||||||
.returnsPromise().resolves({});
|
.resolves({});
|
||||||
iapIsValidatedStub = sinon.stub(iapModule, 'isValidated')
|
iapIsValidatedStub = sinon.stub(iapModule, 'isValidated')
|
||||||
.returns(true);
|
.returns(true);
|
||||||
iapGetPurchaseDataStub = sinon.stub(iapModule, 'getPurchaseData')
|
iapGetPurchaseDataStub = sinon.stub(iapModule, 'getPurchaseData')
|
||||||
.returns([{productId: 'com.habitrpg.ios.Habitica.21gems',
|
.returns([{productId: 'com.habitrpg.ios.Habitica.21gems',
|
||||||
transactionId: token,
|
transactionId: token,
|
||||||
}]);
|
}]);
|
||||||
paymentBuyGemsStub = sinon.stub(payments, 'buyGems').returnsPromise().resolves({});
|
paymentBuyGemsStub = sinon.stub(payments, 'buyGems').resolves({});
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@@ -57,8 +57,20 @@ describe('Apple Payments', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should throw an error if getPurchaseData is invalid', async () => {
|
||||||
|
iapGetPurchaseDataStub.restore();
|
||||||
|
iapGetPurchaseDataStub = sinon.stub(iapModule, 'getPurchaseData').returns([]);
|
||||||
|
|
||||||
|
await expect(applePayments.verifyGemPurchase(user, receipt, headers))
|
||||||
|
.to.eventually.be.rejected.and.to.eql({
|
||||||
|
httpCode: 401,
|
||||||
|
name: 'NotAuthorized',
|
||||||
|
message: applePayments.constants.RESPONSE_NO_ITEM_PURCHASED,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('errors if the user cannot purchase gems', async () => {
|
it('errors if the user cannot purchase gems', async () => {
|
||||||
sinon.stub(user, 'canGetGems').returnsPromise().resolves(false);
|
sinon.stub(user, 'canGetGems').resolves(false);
|
||||||
await expect(applePayments.verifyGemPurchase(user, receipt, headers))
|
await expect(applePayments.verifyGemPurchase(user, receipt, headers))
|
||||||
.to.eventually.be.rejected.and.to.eql({
|
.to.eventually.be.rejected.and.to.eql({
|
||||||
httpCode: 401,
|
httpCode: 401,
|
||||||
@@ -69,27 +81,76 @@ describe('Apple Payments', () => {
|
|||||||
user.canGetGems.restore();
|
user.canGetGems.restore();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('purchases gems', async () => {
|
it('errors if amount does not exist', async () => {
|
||||||
sinon.stub(user, 'canGetGems').returnsPromise().resolves(true);
|
sinon.stub(user, 'canGetGems').resolves(true);
|
||||||
await applePayments.verifyGemPurchase(user, receipt, headers);
|
iapGetPurchaseDataStub.restore();
|
||||||
|
iapGetPurchaseDataStub = sinon.stub(iapModule, 'getPurchaseData')
|
||||||
|
.returns([{productId: 'badProduct',
|
||||||
|
transactionId: token,
|
||||||
|
}]);
|
||||||
|
|
||||||
expect(iapSetupStub).to.be.calledOnce;
|
await expect(applePayments.verifyGemPurchase(user, receipt, headers))
|
||||||
expect(iapValidateStub).to.be.calledOnce;
|
.to.eventually.be.rejected.and.to.eql({
|
||||||
expect(iapValidateStub).to.be.calledWith(iap.APPLE, receipt);
|
httpCode: 401,
|
||||||
expect(iapIsValidatedStub).to.be.calledOnce;
|
name: 'NotAuthorized',
|
||||||
expect(iapIsValidatedStub).to.be.calledWith({});
|
message: applePayments.constants.RESPONSE_INVALID_ITEM,
|
||||||
expect(iapGetPurchaseDataStub).to.be.calledOnce;
|
});
|
||||||
|
|
||||||
expect(paymentBuyGemsStub).to.be.calledOnce;
|
|
||||||
expect(paymentBuyGemsStub).to.be.calledWith({
|
|
||||||
user,
|
|
||||||
paymentMethod: applePayments.constants.PAYMENT_METHOD_APPLE,
|
|
||||||
amount: 5.25,
|
|
||||||
headers,
|
|
||||||
});
|
|
||||||
expect(user.canGetGems).to.be.calledOnce;
|
|
||||||
user.canGetGems.restore();
|
user.canGetGems.restore();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const gemsCanPurchase = [
|
||||||
|
{
|
||||||
|
productId: 'com.habitrpg.ios.Habitica.4gems',
|
||||||
|
amount: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
productId: 'com.habitrpg.ios.Habitica.20gems',
|
||||||
|
amount: 5.25,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
productId: 'com.habitrpg.ios.Habitica.21gems',
|
||||||
|
amount: 5.25,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
productId: 'com.habitrpg.ios.Habitica.42gems',
|
||||||
|
amount: 10.5,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
productId: 'com.habitrpg.ios.Habitica.84gems',
|
||||||
|
amount: 21,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
gemsCanPurchase.forEach(gemTest => {
|
||||||
|
it(`purchases ${gemTest.productId} gems`, async () => {
|
||||||
|
iapGetPurchaseDataStub.restore();
|
||||||
|
iapGetPurchaseDataStub = sinon.stub(iapModule, 'getPurchaseData')
|
||||||
|
.returns([{productId: gemTest.productId,
|
||||||
|
transactionId: token,
|
||||||
|
}]);
|
||||||
|
|
||||||
|
sinon.stub(user, 'canGetGems').resolves(true);
|
||||||
|
await applePayments.verifyGemPurchase(user, receipt, headers);
|
||||||
|
|
||||||
|
expect(iapSetupStub).to.be.calledOnce;
|
||||||
|
expect(iapValidateStub).to.be.calledOnce;
|
||||||
|
expect(iapValidateStub).to.be.calledWith(iap.APPLE, receipt);
|
||||||
|
expect(iapIsValidatedStub).to.be.calledOnce;
|
||||||
|
expect(iapIsValidatedStub).to.be.calledWith({});
|
||||||
|
expect(iapGetPurchaseDataStub).to.be.calledOnce;
|
||||||
|
|
||||||
|
expect(paymentBuyGemsStub).to.be.calledOnce;
|
||||||
|
expect(paymentBuyGemsStub).to.be.calledWith({
|
||||||
|
user,
|
||||||
|
paymentMethod: applePayments.constants.PAYMENT_METHOD_APPLE,
|
||||||
|
amount: gemTest.amount,
|
||||||
|
headers,
|
||||||
|
});
|
||||||
|
expect(user.canGetGems).to.be.calledOnce;
|
||||||
|
user.canGetGems.restore();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('subscribe', () => {
|
describe('subscribe', () => {
|
||||||
@@ -106,9 +167,9 @@ describe('Apple Payments', () => {
|
|||||||
nextPaymentProcessing = moment.utc().add({days: 2});
|
nextPaymentProcessing = moment.utc().add({days: 2});
|
||||||
|
|
||||||
iapSetupStub = sinon.stub(iapModule, 'setup')
|
iapSetupStub = sinon.stub(iapModule, 'setup')
|
||||||
.returnsPromise().resolves();
|
.resolves();
|
||||||
iapValidateStub = sinon.stub(iapModule, 'validate')
|
iapValidateStub = sinon.stub(iapModule, 'validate')
|
||||||
.returnsPromise().resolves({});
|
.resolves({});
|
||||||
iapIsValidatedStub = sinon.stub(iapModule, 'isValidated')
|
iapIsValidatedStub = sinon.stub(iapModule, 'isValidated')
|
||||||
.returns(true);
|
.returns(true);
|
||||||
iapGetPurchaseDataStub = sinon.stub(iapModule, 'getPurchaseData')
|
iapGetPurchaseDataStub = sinon.stub(iapModule, 'getPurchaseData')
|
||||||
@@ -125,7 +186,7 @@ describe('Apple Payments', () => {
|
|||||||
productId: sku,
|
productId: sku,
|
||||||
transactionId: token,
|
transactionId: token,
|
||||||
}]);
|
}]);
|
||||||
paymentsCreateSubscritionStub = sinon.stub(payments, 'createSubscription').returnsPromise().resolves({});
|
paymentsCreateSubscritionStub = sinon.stub(payments, 'createSubscription').resolves({});
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@@ -133,7 +194,16 @@ describe('Apple Payments', () => {
|
|||||||
iapModule.validate.restore();
|
iapModule.validate.restore();
|
||||||
iapModule.isValidated.restore();
|
iapModule.isValidated.restore();
|
||||||
iapModule.getPurchaseData.restore();
|
iapModule.getPurchaseData.restore();
|
||||||
payments.createSubscription.restore();
|
if (payments.createSubscription.restore) payments.createSubscription.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw an error if sku is empty', async () => {
|
||||||
|
await expect(applePayments.subscribe('', user, receipt, headers, nextPaymentProcessing))
|
||||||
|
.to.eventually.be.rejected.and.to.eql({
|
||||||
|
httpCode: 400,
|
||||||
|
name: 'BadRequest',
|
||||||
|
message: i18n.t('missingSubscriptionCode'),
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw an error if receipt is invalid', async () => {
|
it('should throw an error if receipt is invalid', async () => {
|
||||||
@@ -149,26 +219,69 @@ describe('Apple Payments', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('creates a user subscription', async () => {
|
const subOptions = [
|
||||||
|
{
|
||||||
|
sku: 'subscription1month',
|
||||||
|
subKey: 'basic_earned',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sku: 'com.habitrpg.ios.habitica.subscription.3month',
|
||||||
|
subKey: 'basic_3mo',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sku: 'com.habitrpg.ios.habitica.subscription.6month',
|
||||||
|
subKey: 'basic_6mo',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sku: 'com.habitrpg.ios.habitica.subscription.12month',
|
||||||
|
subKey: 'basic_12mo',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
subOptions.forEach(option => {
|
||||||
|
it(`creates a user subscription for ${option.sku}`, async () => {
|
||||||
|
iapModule.getPurchaseData.restore();
|
||||||
|
iapGetPurchaseDataStub = sinon.stub(iapModule, 'getPurchaseData')
|
||||||
|
.returns([{
|
||||||
|
expirationDate: moment.utc().add({day: 1}).toDate(),
|
||||||
|
productId: option.sku,
|
||||||
|
transactionId: token,
|
||||||
|
}]);
|
||||||
|
sub = common.content.subscriptionBlocks[option.subKey];
|
||||||
|
|
||||||
|
await applePayments.subscribe(option.sku, user, receipt, headers, nextPaymentProcessing);
|
||||||
|
|
||||||
|
expect(iapSetupStub).to.be.calledOnce;
|
||||||
|
expect(iapValidateStub).to.be.calledOnce;
|
||||||
|
expect(iapValidateStub).to.be.calledWith(iap.APPLE, receipt);
|
||||||
|
expect(iapIsValidatedStub).to.be.calledOnce;
|
||||||
|
expect(iapIsValidatedStub).to.be.calledWith({});
|
||||||
|
expect(iapGetPurchaseDataStub).to.be.calledOnce;
|
||||||
|
|
||||||
|
expect(paymentsCreateSubscritionStub).to.be.calledOnce;
|
||||||
|
expect(paymentsCreateSubscritionStub).to.be.calledWith({
|
||||||
|
user,
|
||||||
|
customerId: token,
|
||||||
|
paymentMethod: applePayments.constants.PAYMENT_METHOD_APPLE,
|
||||||
|
sub,
|
||||||
|
headers,
|
||||||
|
additionalData: receipt,
|
||||||
|
nextPaymentProcessing,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('errors when a user is already subscribed', async () => {
|
||||||
|
payments.createSubscription.restore();
|
||||||
|
user = new User();
|
||||||
|
|
||||||
await applePayments.subscribe(sku, user, receipt, headers, nextPaymentProcessing);
|
await applePayments.subscribe(sku, user, receipt, headers, nextPaymentProcessing);
|
||||||
|
|
||||||
expect(iapSetupStub).to.be.calledOnce;
|
await expect(applePayments.subscribe(sku, user, receipt, headers, nextPaymentProcessing))
|
||||||
expect(iapValidateStub).to.be.calledOnce;
|
.to.eventually.be.rejected.and.to.eql({
|
||||||
expect(iapValidateStub).to.be.calledWith(iap.APPLE, receipt);
|
httpCode: 401,
|
||||||
expect(iapIsValidatedStub).to.be.calledOnce;
|
name: 'NotAuthorized',
|
||||||
expect(iapIsValidatedStub).to.be.calledWith({});
|
message: applePayments.constants.RESPONSE_ALREADY_USED,
|
||||||
expect(iapGetPurchaseDataStub).to.be.calledOnce;
|
});
|
||||||
|
|
||||||
expect(paymentsCreateSubscritionStub).to.be.calledOnce;
|
|
||||||
expect(paymentsCreateSubscritionStub).to.be.calledWith({
|
|
||||||
user,
|
|
||||||
customerId: token,
|
|
||||||
paymentMethod: applePayments.constants.PAYMENT_METHOD_APPLE,
|
|
||||||
sub,
|
|
||||||
headers,
|
|
||||||
additionalData: receipt,
|
|
||||||
nextPaymentProcessing,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -184,9 +297,9 @@ describe('Apple Payments', () => {
|
|||||||
expirationDate = moment.utc();
|
expirationDate = moment.utc();
|
||||||
|
|
||||||
iapSetupStub = sinon.stub(iapModule, 'setup')
|
iapSetupStub = sinon.stub(iapModule, 'setup')
|
||||||
.returnsPromise().resolves();
|
.resolves();
|
||||||
iapValidateStub = sinon.stub(iapModule, 'validate')
|
iapValidateStub = sinon.stub(iapModule, 'validate')
|
||||||
.returnsPromise().resolves({
|
.resolves({
|
||||||
expirationDate,
|
expirationDate,
|
||||||
});
|
});
|
||||||
iapGetPurchaseDataStub = sinon.stub(iapModule, 'getPurchaseData')
|
iapGetPurchaseDataStub = sinon.stub(iapModule, 'getPurchaseData')
|
||||||
@@ -201,7 +314,7 @@ describe('Apple Payments', () => {
|
|||||||
user.purchased.plan.planId = subKey;
|
user.purchased.plan.planId = subKey;
|
||||||
user.purchased.plan.additionalData = receipt;
|
user.purchased.plan.additionalData = receipt;
|
||||||
|
|
||||||
paymentCancelSubscriptionSpy = sinon.stub(payments, 'cancelSubscription').returnsPromise().resolves({});
|
paymentCancelSubscriptionSpy = sinon.stub(payments, 'cancelSubscription').resolves({});
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(function () {
|
afterEach(function () {
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
/* eslint-disable camelcase */
|
/* eslint-disable camelcase */
|
||||||
import iapModule from '../../../../../website/server/libs/inAppPurchases';
|
import iapModule from '../../../../../website/server/libs/inAppPurchases';
|
||||||
import payments from '../../../../../website/server/libs/payments';
|
import payments from '../../../../../website/server/libs/payments/payments';
|
||||||
import googlePayments from '../../../../../website/server/libs/googlePayments';
|
import googlePayments from '../../../../../website/server/libs/payments/google';
|
||||||
import iap from '../../../../../website/server/libs/inAppPurchases';
|
import iap from '../../../../../website/server/libs/inAppPurchases';
|
||||||
import {model as User} from '../../../../../website/server/models/user';
|
import {model as User} from '../../../../../website/server/models/user';
|
||||||
import common from '../../../../../website/common';
|
import common from '../../../../../website/common';
|
||||||
@@ -24,12 +24,12 @@ describe('Google Payments', () => {
|
|||||||
headers = {};
|
headers = {};
|
||||||
|
|
||||||
iapSetupStub = sinon.stub(iapModule, 'setup')
|
iapSetupStub = sinon.stub(iapModule, 'setup')
|
||||||
.returnsPromise().resolves();
|
.resolves();
|
||||||
iapValidateStub = sinon.stub(iapModule, 'validate')
|
iapValidateStub = sinon.stub(iapModule, 'validate')
|
||||||
.returnsPromise().resolves({});
|
.resolves({});
|
||||||
iapIsValidatedStub = sinon.stub(iapModule, 'isValidated')
|
iapIsValidatedStub = sinon.stub(iapModule, 'isValidated')
|
||||||
.returns(true);
|
.returns(true);
|
||||||
paymentBuyGemsStub = sinon.stub(payments, 'buyGems').returnsPromise().resolves({});
|
paymentBuyGemsStub = sinon.stub(payments, 'buyGems').resolves({});
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@@ -64,7 +64,7 @@ describe('Google Payments', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should throw an error if user cannot purchase gems', async () => {
|
it('should throw an error if user cannot purchase gems', async () => {
|
||||||
sinon.stub(user, 'canGetGems').returnsPromise().resolves(false);
|
sinon.stub(user, 'canGetGems').resolves(false);
|
||||||
|
|
||||||
await expect(googlePayments.verifyGemPurchase(user, receipt, signature, headers))
|
await expect(googlePayments.verifyGemPurchase(user, receipt, signature, headers))
|
||||||
.to.eventually.be.rejected.and.to.eql({
|
.to.eventually.be.rejected.and.to.eql({
|
||||||
@@ -77,7 +77,7 @@ describe('Google Payments', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('purchases gems', async () => {
|
it('purchases gems', async () => {
|
||||||
sinon.stub(user, 'canGetGems').returnsPromise().resolves(true);
|
sinon.stub(user, 'canGetGems').resolves(true);
|
||||||
await googlePayments.verifyGemPurchase(user, receipt, signature, headers);
|
await googlePayments.verifyGemPurchase(user, receipt, signature, headers);
|
||||||
|
|
||||||
expect(iapSetupStub).to.be.calledOnce;
|
expect(iapSetupStub).to.be.calledOnce;
|
||||||
@@ -116,12 +116,12 @@ describe('Google Payments', () => {
|
|||||||
nextPaymentProcessing = moment.utc().add({days: 2});
|
nextPaymentProcessing = moment.utc().add({days: 2});
|
||||||
|
|
||||||
iapSetupStub = sinon.stub(iapModule, 'setup')
|
iapSetupStub = sinon.stub(iapModule, 'setup')
|
||||||
.returnsPromise().resolves();
|
.resolves();
|
||||||
iapValidateStub = sinon.stub(iapModule, 'validate')
|
iapValidateStub = sinon.stub(iapModule, 'validate')
|
||||||
.returnsPromise().resolves({});
|
.resolves({});
|
||||||
iapIsValidatedStub = sinon.stub(iapModule, 'isValidated')
|
iapIsValidatedStub = sinon.stub(iapModule, 'isValidated')
|
||||||
.returns(true);
|
.returns(true);
|
||||||
paymentsCreateSubscritionStub = sinon.stub(payments, 'createSubscription').returnsPromise().resolves({});
|
paymentsCreateSubscritionStub = sinon.stub(payments, 'createSubscription').resolves({});
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@@ -193,9 +193,9 @@ describe('Google Payments', () => {
|
|||||||
expirationDate = moment.utc();
|
expirationDate = moment.utc();
|
||||||
|
|
||||||
iapSetupStub = sinon.stub(iapModule, 'setup')
|
iapSetupStub = sinon.stub(iapModule, 'setup')
|
||||||
.returnsPromise().resolves();
|
.resolves();
|
||||||
iapValidateStub = sinon.stub(iapModule, 'validate')
|
iapValidateStub = sinon.stub(iapModule, 'validate')
|
||||||
.returnsPromise().resolves({
|
.resolves({
|
||||||
expirationDate,
|
expirationDate,
|
||||||
});
|
});
|
||||||
iapGetPurchaseDataStub = sinon.stub(iapModule, 'getPurchaseData')
|
iapGetPurchaseDataStub = sinon.stub(iapModule, 'getPurchaseData')
|
||||||
@@ -210,7 +210,7 @@ describe('Google Payments', () => {
|
|||||||
user.purchased.plan.planId = subKey;
|
user.purchased.plan.planId = subKey;
|
||||||
user.purchased.plan.additionalData = {data: receipt, signature};
|
user.purchased.plan.additionalData = {data: receipt, signature};
|
||||||
|
|
||||||
paymentCancelSubscriptionSpy = sinon.stub(payments, 'cancelSubscription').returnsPromise().resolves({});
|
paymentCancelSubscriptionSpy = sinon.stub(payments, 'cancelSubscription').resolves({});
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(function () {
|
afterEach(function () {
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
|
||||||
import * as sender from '../../../../../../../website/server/libs/email';
|
import * as sender from '../../../../../../website/server/libs/email';
|
||||||
import * as api from '../../../../../../../website/server/libs/payments';
|
import * as api from '../../../../../../website/server/libs/payments/payments';
|
||||||
import { model as User } from '../../../../../../../website/server/models/user';
|
import { model as User } from '../../../../../../website/server/models/user';
|
||||||
import { model as Group } from '../../../../../../../website/server/models/group';
|
import { model as Group } from '../../../../../../website/server/models/group';
|
||||||
import {
|
import {
|
||||||
generateGroup,
|
generateGroup,
|
||||||
} from '../../../../../../helpers/api-unit.helper.js';
|
} from '../../../../../helpers/api-unit.helper.js';
|
||||||
import i18n from '../../../../../../../website/common/script/i18n';
|
import i18n from '../../../../../../website/common/script/i18n';
|
||||||
|
|
||||||
describe('Canceling a subscription for group', () => {
|
describe('Canceling a subscription for group', () => {
|
||||||
let plan, group, user, data;
|
let plan, group, user, data;
|
||||||
@@ -2,16 +2,16 @@ import moment from 'moment';
|
|||||||
import stripeModule from 'stripe';
|
import stripeModule from 'stripe';
|
||||||
import nconf from 'nconf';
|
import nconf from 'nconf';
|
||||||
|
|
||||||
import * as sender from '../../../../../../../website/server/libs/email';
|
import * as sender from '../../../../../../website/server/libs/email';
|
||||||
import * as api from '../../../../../../../website/server/libs/payments';
|
import * as api from '../../../../../../website/server/libs/payments/payments';
|
||||||
import amzLib from '../../../../../../../website/server/libs/amazonPayments';
|
import amzLib from '../../../../../../website/server/libs/payments/amazon';
|
||||||
import stripePayments from '../../../../../../../website/server/libs/stripePayments';
|
import paypalPayments from '../../../../../../website/server/libs/payments/paypal';
|
||||||
import paypalPayments from '../../../../../../../website/server/libs/paypalPayments';
|
import stripePayments from '../../../../../../website/server/libs/payments/stripe';
|
||||||
import { model as User } from '../../../../../../../website/server/models/user';
|
import { model as User } from '../../../../../../website/server/models/user';
|
||||||
import { model as Group } from '../../../../../../../website/server/models/group';
|
import { model as Group } from '../../../../../../website/server/models/group';
|
||||||
import {
|
import {
|
||||||
generateGroup,
|
generateGroup,
|
||||||
} from '../../../../../../helpers/api-unit.helper.js';
|
} from '../../../../../helpers/api-unit.helper.js';
|
||||||
|
|
||||||
describe('Purchasing a group plan for group', () => {
|
describe('Purchasing a group plan for group', () => {
|
||||||
const EMAIL_TEMPLATE_SUBSCRIPTION_TYPE_GOOGLE = 'Google_subscription';
|
const EMAIL_TEMPLATE_SUBSCRIPTION_TYPE_GOOGLE = 'Google_subscription';
|
||||||
@@ -69,11 +69,11 @@ describe('Purchasing a group plan for group', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let subscriptionId = 'subId';
|
let subscriptionId = 'subId';
|
||||||
sinon.stub(stripe.customers, 'del').returnsPromise().resolves({});
|
sinon.stub(stripe.customers, 'del').resolves({});
|
||||||
|
|
||||||
let currentPeriodEndTimeStamp = moment().add(3, 'months').unix();
|
let currentPeriodEndTimeStamp = moment().add(3, 'months').unix();
|
||||||
sinon.stub(stripe.customers, 'retrieve')
|
sinon.stub(stripe.customers, 'retrieve')
|
||||||
.returnsPromise().resolves({
|
.resolves({
|
||||||
subscriptions: {
|
subscriptions: {
|
||||||
data: [{id: subscriptionId, current_period_end: currentPeriodEndTimeStamp}], // eslint-disable-line camelcase
|
data: [{id: subscriptionId, current_period_end: currentPeriodEndTimeStamp}], // eslint-disable-line camelcase
|
||||||
},
|
},
|
||||||
@@ -216,7 +216,6 @@ describe('Purchasing a group plan for group', () => {
|
|||||||
|
|
||||||
it('sends one email to subscribed member of group, stating subscription is cancelled (Amazon)', async () => {
|
it('sends one email to subscribed member of group, stating subscription is cancelled (Amazon)', async () => {
|
||||||
sinon.stub(amzLib, 'getBillingAgreementDetails')
|
sinon.stub(amzLib, 'getBillingAgreementDetails')
|
||||||
.returnsPromise()
|
|
||||||
.resolves({
|
.resolves({
|
||||||
BillingAgreementDetails: {
|
BillingAgreementDetails: {
|
||||||
BillingAgreementStatus: {State: 'Closed'},
|
BillingAgreementStatus: {State: 'Closed'},
|
||||||
@@ -251,9 +250,9 @@ describe('Purchasing a group plan for group', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('sends one email to subscribed member of group, stating subscription is cancelled (PayPal)', async () => {
|
it('sends one email to subscribed member of group, stating subscription is cancelled (PayPal)', async () => {
|
||||||
sinon.stub(paypalPayments, 'paypalBillingAgreementCancel').returnsPromise().resolves({});
|
sinon.stub(paypalPayments, 'paypalBillingAgreementCancel').resolves({});
|
||||||
sinon.stub(paypalPayments, 'paypalBillingAgreementGet')
|
sinon.stub(paypalPayments, 'paypalBillingAgreementGet')
|
||||||
.returnsPromise().resolves({
|
.resolves({
|
||||||
agreement_details: { // eslint-disable-line camelcase
|
agreement_details: { // eslint-disable-line camelcase
|
||||||
next_billing_date: moment().add(3, 'months').toDate(), // eslint-disable-line camelcase
|
next_billing_date: moment().add(3, 'months').toDate(), // eslint-disable-line camelcase
|
||||||
cycles_completed: 1, // eslint-disable-line camelcase
|
cycles_completed: 1, // eslint-disable-line camelcase
|
||||||
@@ -443,14 +442,12 @@ describe('Purchasing a group plan for group', () => {
|
|||||||
|
|
||||||
await api.createSubscription(data);
|
await api.createSubscription(data);
|
||||||
|
|
||||||
let updatedUser = await User.findById(recipient._id).exec();
|
const updatedUser = await User.findById(recipient._id).exec();
|
||||||
|
|
||||||
expect(updatedUser.purchased.plan.extraMonths).to.within(2, 3);
|
expect(updatedUser.purchased.plan.extraMonths).to.within(2, 3);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('adds months to members with existing recurring subscription (Amazon)', async () => {
|
it('adds months to members with existing recurring subscription (Amazon)', async () => {
|
||||||
sinon.stub(amzLib, 'getBillingAgreementDetails')
|
sinon.stub(amzLib, 'getBillingAgreementDetails')
|
||||||
.returnsPromise()
|
|
||||||
.resolves({
|
.resolves({
|
||||||
BillingAgreementDetails: {
|
BillingAgreementDetails: {
|
||||||
BillingAgreementStatus: {State: 'Closed'},
|
BillingAgreementStatus: {State: 'Closed'},
|
||||||
@@ -479,9 +476,9 @@ describe('Purchasing a group plan for group', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('adds months to members with existing recurring subscription (Paypal)', async () => {
|
it('adds months to members with existing recurring subscription (Paypal)', async () => {
|
||||||
sinon.stub(paypalPayments, 'paypalBillingAgreementCancel').returnsPromise().resolves({});
|
sinon.stub(paypalPayments, 'paypalBillingAgreementCancel').resolves({});
|
||||||
sinon.stub(paypalPayments, 'paypalBillingAgreementGet')
|
sinon.stub(paypalPayments, 'paypalBillingAgreementGet')
|
||||||
.returnsPromise().resolves({
|
.resolves({
|
||||||
agreement_details: { // eslint-disable-line camelcase
|
agreement_details: { // eslint-disable-line camelcase
|
||||||
next_billing_date: moment().add(3, 'months').toDate(), // eslint-disable-line camelcase
|
next_billing_date: moment().add(3, 'months').toDate(), // eslint-disable-line camelcase
|
||||||
cycles_completed: 1, // eslint-disable-line camelcase
|
cycles_completed: 1, // eslint-disable-line camelcase
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { model as User } from '../../../../../../website/server/models/user';
|
import { model as User } from '../../../../../website/server/models/user';
|
||||||
|
|
||||||
export async function createNonLeaderGroupMember (group) {
|
export async function createNonLeaderGroupMember (group) {
|
||||||
let nonLeader = new User();
|
let nonLeader = new User();
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
|
||||||
import * as sender from '../../../../../website/server/libs/email';
|
import * as sender from '../../../../../website/server/libs/email';
|
||||||
import * as api from '../../../../../website/server/libs/payments';
|
import * as api from '../../../../../website/server/libs/payments/payments';
|
||||||
import analytics from '../../../../../website/server/libs/analyticsService';
|
import analytics from '../../../../../website/server/libs/analyticsService';
|
||||||
import notifications from '../../../../../website/server/libs/pushNotifications';
|
import notifications from '../../../../../website/server/libs/pushNotifications';
|
||||||
import { model as User } from '../../../../../website/server/models/user';
|
import { model as User } from '../../../../../website/server/models/user';
|
||||||
import { translate as t } from '../../../../helpers/api-v3-integration.helper';
|
import { translate as t } from '../../../../helpers/api-integration/v3';
|
||||||
import {
|
import {
|
||||||
generateGroup,
|
generateGroup,
|
||||||
} from '../../../../helpers/api-unit.helper.js';
|
} from '../../../../helpers/api-unit.helper.js';
|
||||||
@@ -209,8 +209,8 @@ describe('payments/index', () => {
|
|||||||
await api.createSubscription(data);
|
await api.createSubscription(data);
|
||||||
let msg = '\`Hello recipient, sender has sent you 3 months of subscription!\`';
|
let msg = '\`Hello recipient, sender has sent you 3 months of subscription!\`';
|
||||||
|
|
||||||
expect(user.sendMessage).to.be.calledOnce;
|
expect(user.sendMessage).to.be.calledTwice;
|
||||||
expect(user.sendMessage).to.be.calledWith(recipient, { receiverMsg: msg, senderMsg: msg });
|
expect(user.sendMessage).to.be.calledWith(recipient, { receiverMsg: msg, senderMsg: msg, save: false });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('sends an email about the gift', async () => {
|
it('sends an email about the gift', async () => {
|
||||||
@@ -247,6 +247,77 @@ describe('payments/index', () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
context('Winter 2018-19 Gift-1-Get-1 Promotion', async () => {
|
||||||
|
it('creates a gift subscription for purchaser and recipient if none exist', async () => {
|
||||||
|
await api.createSubscription(data);
|
||||||
|
|
||||||
|
expect(user.items.pets['Jackalope-RoyalPurple']).to.eql(5);
|
||||||
|
expect(user.purchased.plan.customerId).to.eql('Gift');
|
||||||
|
expect(user.purchased.plan.dateTerminated).to.exist;
|
||||||
|
expect(user.purchased.plan.dateUpdated).to.exist;
|
||||||
|
expect(user.purchased.plan.dateCreated).to.exist;
|
||||||
|
|
||||||
|
expect(recipient.items.pets['Jackalope-RoyalPurple']).to.eql(5);
|
||||||
|
expect(recipient.purchased.plan.customerId).to.eql('Gift');
|
||||||
|
expect(recipient.purchased.plan.dateTerminated).to.exist;
|
||||||
|
expect(recipient.purchased.plan.dateUpdated).to.exist;
|
||||||
|
expect(recipient.purchased.plan.dateCreated).to.exist;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('adds extraMonths to existing subscription for purchaser and creates a gift subscription for recipient without sub', async () => {
|
||||||
|
user.purchased.plan = plan;
|
||||||
|
|
||||||
|
expect(user.purchased.plan.extraMonths).to.eql(0);
|
||||||
|
|
||||||
|
await api.createSubscription(data);
|
||||||
|
|
||||||
|
expect(user.purchased.plan.extraMonths).to.eql(3);
|
||||||
|
|
||||||
|
expect(recipient.items.pets['Jackalope-RoyalPurple']).to.eql(5);
|
||||||
|
expect(recipient.purchased.plan.customerId).to.eql('Gift');
|
||||||
|
expect(recipient.purchased.plan.dateTerminated).to.exist;
|
||||||
|
expect(recipient.purchased.plan.dateUpdated).to.exist;
|
||||||
|
expect(recipient.purchased.plan.dateCreated).to.exist;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('adds extraMonths to existing subscription for recipient and creates a gift subscription for purchaser without sub', async () => {
|
||||||
|
recipient.purchased.plan = plan;
|
||||||
|
|
||||||
|
expect(recipient.purchased.plan.extraMonths).to.eql(0);
|
||||||
|
|
||||||
|
await api.createSubscription(data);
|
||||||
|
|
||||||
|
expect(recipient.purchased.plan.extraMonths).to.eql(3);
|
||||||
|
|
||||||
|
expect(user.items.pets['Jackalope-RoyalPurple']).to.eql(5);
|
||||||
|
expect(user.purchased.plan.customerId).to.eql('Gift');
|
||||||
|
expect(user.purchased.plan.dateTerminated).to.exist;
|
||||||
|
expect(user.purchased.plan.dateUpdated).to.exist;
|
||||||
|
expect(user.purchased.plan.dateCreated).to.exist;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('adds extraMonths to existing subscriptions for purchaser and recipient', async () => {
|
||||||
|
user.purchased.plan = plan;
|
||||||
|
recipient.purchased.plan = plan;
|
||||||
|
|
||||||
|
expect(user.purchased.plan.extraMonths).to.eql(0);
|
||||||
|
expect(recipient.purchased.plan.extraMonths).to.eql(0);
|
||||||
|
|
||||||
|
await api.createSubscription(data);
|
||||||
|
|
||||||
|
expect(user.purchased.plan.extraMonths).to.eql(3);
|
||||||
|
expect(recipient.purchased.plan.extraMonths).to.eql(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sends a private message about the promotion', async () => {
|
||||||
|
await api.createSubscription(data);
|
||||||
|
let msg = '\`Hello sender, you received 3 months of subscription as part of our holiday gift-giving promotion!\`';
|
||||||
|
|
||||||
|
expect(user.sendMessage).to.be.calledTwice;
|
||||||
|
expect(user.sendMessage).to.be.calledWith(user, { senderMsg: msg });
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
context('Purchasing a subscription for self', () => {
|
context('Purchasing a subscription for self', () => {
|
||||||
@@ -446,6 +517,19 @@ describe('payments/index', () => {
|
|||||||
fakeClock.restore();
|
fakeClock.restore();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('does not add a notification for mystery items if none was awarded', async () => {
|
||||||
|
const noMysteryItemTimeframe = 1462183920000; // May 2nd 2016
|
||||||
|
let fakeClock = sinon.useFakeTimers(noMysteryItemTimeframe);
|
||||||
|
data = { paymentMethod: 'PaymentMethod', user, sub: { key: 'basic_3mo' } };
|
||||||
|
|
||||||
|
await api.createSubscription(data);
|
||||||
|
|
||||||
|
expect(user.purchased.plan.mysteryItems).to.have.a.lengthOf(0);
|
||||||
|
expect(user.notifications.find(n => n.type === 'NEW_MYSTERY_ITEMS')).to.be.undefined;
|
||||||
|
|
||||||
|
fakeClock.restore();
|
||||||
|
});
|
||||||
|
|
||||||
it('does not award mystery item when user already owns the item', async () => {
|
it('does not award mystery item when user already owns the item', async () => {
|
||||||
let mayMysteryItemTimeframe = 1464725113000; // May 31st 2016
|
let mayMysteryItemTimeframe = 1464725113000; // May 31st 2016
|
||||||
let fakeClock = sinon.useFakeTimers(mayMysteryItemTimeframe);
|
let fakeClock = sinon.useFakeTimers(mayMysteryItemTimeframe);
|
||||||
@@ -629,7 +713,16 @@ describe('payments/index', () => {
|
|||||||
await api.buyGems(data);
|
await api.buyGems(data);
|
||||||
let msg = '\`Hello recipient, sender has sent you 4 gems!\`';
|
let msg = '\`Hello recipient, sender has sent you 4 gems!\`';
|
||||||
|
|
||||||
expect(user.sendMessage).to.be.calledWith(recipient, { receiverMsg: msg, senderMsg: msg });
|
expect(user.sendMessage).to.be.calledWith(recipient, { receiverMsg: msg, senderMsg: msg, save: false });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sends a message from purchaser to recipient wtih custom message', async () => {
|
||||||
|
data.gift.message = 'giftmessage';
|
||||||
|
|
||||||
|
await api.buyGems(data);
|
||||||
|
|
||||||
|
const msg = `\`Hello recipient, sender has sent you 4 gems!\` ${data.gift.message}`;
|
||||||
|
expect(user.sendMessage).to.be.calledWith(recipient, { receiverMsg: msg, senderMsg: msg, save: false });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('sends a push notification if user did not gift to self', async () => {
|
it('sends a push notification if user did not gift to self', async () => {
|
||||||
@@ -658,7 +751,7 @@ describe('payments/index', () => {
|
|||||||
return `\`${messageContent}\``;
|
return `\`${messageContent}\``;
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(user.sendMessage).to.be.calledWith(recipient, { receiverMsg: recipientsMessageContent, senderMsg: sendersMessageContent });
|
expect(user.sendMessage).to.be.calledWith(recipient, { receiverMsg: recipientsMessageContent, senderMsg: sendersMessageContent, save: false });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
/* eslint-disable camelcase */
|
/* eslint-disable camelcase */
|
||||||
import payments from '../../../../../../../website/server/libs/payments';
|
import paypalPayments from '../../../../../../website/server/libs/payments/paypal';
|
||||||
import paypalPayments from '../../../../../../../website/server/libs/paypalPayments';
|
import payments from '../../../../../../website/server/libs/payments/payments';
|
||||||
import { model as User } from '../../../../../../../website/server/models/user';
|
import { model as User } from '../../../../../../website/server/models/user';
|
||||||
|
|
||||||
describe('checkout success', () => {
|
describe('checkout success', () => {
|
||||||
const subKey = 'basic_3mo';
|
const subKey = 'basic_3mo';
|
||||||
@@ -13,9 +13,9 @@ describe('checkout success', () => {
|
|||||||
customerId = 'customerId-test';
|
customerId = 'customerId-test';
|
||||||
paymentId = 'paymentId-test';
|
paymentId = 'paymentId-test';
|
||||||
|
|
||||||
paypalPaymentExecuteStub = sinon.stub(paypalPayments, 'paypalPaymentExecute').returnsPromise().resolves({});
|
paypalPaymentExecuteStub = sinon.stub(paypalPayments, 'paypalPaymentExecute').resolves({});
|
||||||
paymentBuyGemsStub = sinon.stub(payments, 'buyGems').returnsPromise().resolves({});
|
paymentBuyGemsStub = sinon.stub(payments, 'buyGems').resolves({});
|
||||||
paymentsCreateSubscritionStub = sinon.stub(payments, 'createSubscription').returnsPromise().resolves({});
|
paymentsCreateSubscritionStub = sinon.stub(payments, 'createSubscription').resolves({});
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
/* eslint-disable camelcase */
|
/* eslint-disable camelcase */
|
||||||
import nconf from 'nconf';
|
import nconf from 'nconf';
|
||||||
|
|
||||||
import paypalPayments from '../../../../../../../website/server/libs/paypalPayments';
|
import paypalPayments from '../../../../../../website/server/libs/payments/paypal';
|
||||||
import { model as User } from '../../../../../../../website/server/models/user';
|
import { model as User } from '../../../../../../website/server/models/user';
|
||||||
import common from '../../../../../../../website/common';
|
import common from '../../../../../../website/common';
|
||||||
|
|
||||||
const BASE_URL = nconf.get('BASE_URL');
|
const BASE_URL = nconf.get('BASE_URL');
|
||||||
const i18n = common.i18n;
|
const i18n = common.i18n;
|
||||||
@@ -15,6 +15,7 @@ describe('checkout', () => {
|
|||||||
|
|
||||||
function getPaypalCreateOptions (description, amount) {
|
function getPaypalCreateOptions (description, amount) {
|
||||||
return {
|
return {
|
||||||
|
experience_profile_id: 'xp_profile_id',
|
||||||
intent: 'sale',
|
intent: 'sale',
|
||||||
payer: { payment_method: 'Paypal' },
|
payer: { payment_method: 'Paypal' },
|
||||||
redirect_urls: {
|
redirect_urls: {
|
||||||
@@ -42,7 +43,7 @@ describe('checkout', () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
approvalHerf = 'approval_href';
|
approvalHerf = 'approval_href';
|
||||||
paypalPaymentCreateStub = sinon.stub(paypalPayments, 'paypalPaymentCreate')
|
paypalPaymentCreateStub = sinon.stub(paypalPayments, 'paypalPaymentCreate')
|
||||||
.returnsPromise().resolves({
|
.resolves({
|
||||||
links: [{ rel: 'approval_url', href: approvalHerf }],
|
links: [{ rel: 'approval_url', href: approvalHerf }],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -80,7 +81,7 @@ describe('checkout', () => {
|
|||||||
|
|
||||||
it('should error if the user cannot get gems', async () => {
|
it('should error if the user cannot get gems', async () => {
|
||||||
let user = new User();
|
let user = new User();
|
||||||
sinon.stub(user, 'canGetGems').returnsPromise().resolves(false);
|
sinon.stub(user, 'canGetGems').resolves(false);
|
||||||
|
|
||||||
await expect(paypalPayments.checkout({user})).to.eventually.be.rejected.and.to.eql({
|
await expect(paypalPayments.checkout({user})).to.eventually.be.rejected.and.to.eql({
|
||||||
httpCode: 401,
|
httpCode: 401,
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
/* eslint-disable camelcase */
|
/* eslint-disable camelcase */
|
||||||
import payments from '../../../../../../../website/server/libs/payments';
|
import paypalPayments from '../../../../../../website/server/libs/payments/paypal';
|
||||||
import paypalPayments from '../../../../../../../website/server/libs/paypalPayments';
|
import payments from '../../../../../../website/server/libs/payments/payments';
|
||||||
import {
|
import {
|
||||||
generateGroup,
|
generateGroup,
|
||||||
} from '../../../../../../helpers/api-unit.helper.js';
|
} from '../../../../../helpers/api-unit.helper.js';
|
||||||
import { model as User } from '../../../../../../../website/server/models/user';
|
import { model as User } from '../../../../../../website/server/models/user';
|
||||||
|
|
||||||
describe('ipn', () => {
|
describe('ipn', () => {
|
||||||
const subKey = 'basic_3mo';
|
const subKey = 'basic_3mo';
|
||||||
@@ -34,8 +34,8 @@ describe('ipn', () => {
|
|||||||
group.purchased.plan.lastBillingDate = new Date();
|
group.purchased.plan.lastBillingDate = new Date();
|
||||||
await group.save();
|
await group.save();
|
||||||
|
|
||||||
ipnVerifyAsyncStub = sinon.stub(paypalPayments, 'ipnVerifyAsync').returnsPromise().resolves({});
|
ipnVerifyAsyncStub = sinon.stub(paypalPayments, 'ipnVerifyAsync').resolves({});
|
||||||
paymentCancelSubscriptionSpy = sinon.stub(payments, 'cancelSubscription').returnsPromise().resolves({});
|
paymentCancelSubscriptionSpy = sinon.stub(payments, 'cancelSubscription').resolves({});
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(function () {
|
afterEach(function () {
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
/* eslint-disable camelcase */
|
/* eslint-disable camelcase */
|
||||||
import payments from '../../../../../../../website/server/libs/payments';
|
import paypalPayments from '../../../../../../website/server/libs/payments/paypal';
|
||||||
import paypalPayments from '../../../../../../../website/server/libs/paypalPayments';
|
import payments from '../../../../../../website/server/libs/payments/payments';
|
||||||
import {
|
import {
|
||||||
generateGroup,
|
generateGroup,
|
||||||
} from '../../../../../../helpers/api-unit.helper.js';
|
} from '../../../../../helpers/api-unit.helper.js';
|
||||||
import { model as User } from '../../../../../../../website/server/models/user';
|
import { model as User } from '../../../../../../website/server/models/user';
|
||||||
import common from '../../../../../../../website/common';
|
import common from '../../../../../../website/common';
|
||||||
import { createNonLeaderGroupMember } from '../paymentHelpers';
|
import { createNonLeaderGroupMember } from '../paymentHelpers';
|
||||||
|
|
||||||
const i18n = common.i18n;
|
const i18n = common.i18n;
|
||||||
@@ -38,15 +38,15 @@ describe('subscribeCancel', () => {
|
|||||||
|
|
||||||
nextBillingDate = new Date();
|
nextBillingDate = new Date();
|
||||||
|
|
||||||
paypalBillingAgreementCancelStub = sinon.stub(paypalPayments, 'paypalBillingAgreementCancel').returnsPromise().resolves({});
|
paypalBillingAgreementCancelStub = sinon.stub(paypalPayments, 'paypalBillingAgreementCancel').resolves({});
|
||||||
paypalBillingAgreementGetStub = sinon.stub(paypalPayments, 'paypalBillingAgreementGet')
|
paypalBillingAgreementGetStub = sinon.stub(paypalPayments, 'paypalBillingAgreementGet')
|
||||||
.returnsPromise().resolves({
|
.resolves({
|
||||||
agreement_details: {
|
agreement_details: {
|
||||||
next_billing_date: nextBillingDate,
|
next_billing_date: nextBillingDate,
|
||||||
cycles_completed: 1,
|
cycles_completed: 1,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
paymentCancelSubscriptionSpy = sinon.stub(payments, 'cancelSubscription').returnsPromise().resolves({});
|
paymentCancelSubscriptionSpy = sinon.stub(payments, 'cancelSubscription').resolves({});
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(function () {
|
afterEach(function () {
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
/* eslint-disable camelcase */
|
/* eslint-disable camelcase */
|
||||||
import payments from '../../../../../../../website/server/libs/payments';
|
import paypalPayments from '../../../../../../website/server/libs/payments/paypal';
|
||||||
import paypalPayments from '../../../../../../../website/server/libs/paypalPayments';
|
import payments from '../../../../../../website/server/libs/payments/payments';
|
||||||
import {
|
import {
|
||||||
generateGroup,
|
generateGroup,
|
||||||
} from '../../../../../../helpers/api-unit.helper.js';
|
} from '../../../../../helpers/api-unit.helper.js';
|
||||||
import { model as User } from '../../../../../../../website/server/models/user';
|
import { model as User } from '../../../../../../website/server/models/user';
|
||||||
import common from '../../../../../../../website/common';
|
import common from '../../../../../../website/common';
|
||||||
|
|
||||||
describe('subscribeSuccess', () => {
|
describe('subscribeSuccess', () => {
|
||||||
const subKey = 'basic_3mo';
|
const subKey = 'basic_3mo';
|
||||||
@@ -28,10 +28,10 @@ describe('subscribeSuccess', () => {
|
|||||||
customerId = 'test-customerId';
|
customerId = 'test-customerId';
|
||||||
|
|
||||||
paypalBillingAgreementExecuteStub = sinon.stub(paypalPayments, 'paypalBillingAgreementExecute')
|
paypalBillingAgreementExecuteStub = sinon.stub(paypalPayments, 'paypalBillingAgreementExecute')
|
||||||
.returnsPromise({}).resolves({
|
.resolves({
|
||||||
id: customerId,
|
id: customerId,
|
||||||
});
|
});
|
||||||
paymentsCreateSubscritionStub = sinon.stub(payments, 'createSubscription').returnsPromise().resolves({});
|
paymentsCreateSubscritionStub = sinon.stub(payments, 'createSubscription').resolves({});
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@@ -2,9 +2,9 @@
|
|||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import cc from 'coupon-code';
|
import cc from 'coupon-code';
|
||||||
|
|
||||||
import paypalPayments from '../../../../../../../website/server/libs/paypalPayments';
|
import paypalPayments from '../../../../../../website/server/libs/payments/paypal';
|
||||||
import { model as Coupon } from '../../../../../../../website/server/models/coupon';
|
import { model as Coupon } from '../../../../../../website/server/models/coupon';
|
||||||
import common from '../../../../../../../website/common';
|
import common from '../../../../../../website/common';
|
||||||
|
|
||||||
const i18n = common.i18n;
|
const i18n = common.i18n;
|
||||||
|
|
||||||
@@ -18,7 +18,7 @@ describe('subscribe', () => {
|
|||||||
sub = Object.assign({}, common.content.subscriptionBlocks[subKey]);
|
sub = Object.assign({}, common.content.subscriptionBlocks[subKey]);
|
||||||
|
|
||||||
paypalBillingAgreementCreateStub = sinon.stub(paypalPayments, 'paypalBillingAgreementCreate')
|
paypalBillingAgreementCreateStub = sinon.stub(paypalPayments, 'paypalBillingAgreementCreate')
|
||||||
.returnsPromise().resolves({
|
.resolves({
|
||||||
links: [{ rel: 'approval_url', href: approvalHerf }],
|
links: [{ rel: 'approval_url', href: approvalHerf }],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -2,11 +2,11 @@ import stripeModule from 'stripe';
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
generateGroup,
|
generateGroup,
|
||||||
} from '../../../../../../helpers/api-unit.helper.js';
|
} from '../../../../../helpers/api-unit.helper.js';
|
||||||
import { model as User } from '../../../../../../../website/server/models/user';
|
import { model as User } from '../../../../../../website/server/models/user';
|
||||||
import stripePayments from '../../../../../../../website/server/libs/stripePayments';
|
import stripePayments from '../../../../../../website/server/libs/payments/stripe';
|
||||||
import payments from '../../../../../../../website/server/libs/payments';
|
import payments from '../../../../../../website/server/libs/payments/payments';
|
||||||
import common from '../../../../../../../website/common';
|
import common from '../../../../../../website/common';
|
||||||
|
|
||||||
const i18n = common.i18n;
|
const i18n = common.i18n;
|
||||||
|
|
||||||
@@ -82,12 +82,12 @@ describe('cancel subscription', () => {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
subscriptionId = 'subId';
|
subscriptionId = 'subId';
|
||||||
stripeDeleteCustomerStub = sinon.stub(stripe.customers, 'del').returnsPromise().resolves({});
|
stripeDeleteCustomerStub = sinon.stub(stripe.customers, 'del').resolves({});
|
||||||
paymentsCancelSubStub = sinon.stub(payments, 'cancelSubscription').returnsPromise().resolves({});
|
paymentsCancelSubStub = sinon.stub(payments, 'cancelSubscription').resolves({});
|
||||||
|
|
||||||
currentPeriodEndTimeStamp = (new Date()).getTime();
|
currentPeriodEndTimeStamp = (new Date()).getTime();
|
||||||
stripeRetrieveStub = sinon.stub(stripe.customers, 'retrieve')
|
stripeRetrieveStub = sinon.stub(stripe.customers, 'retrieve')
|
||||||
.returnsPromise().resolves({
|
.resolves({
|
||||||
subscriptions: {
|
subscriptions: {
|
||||||
data: [{id: subscriptionId, current_period_end: currentPeriodEndTimeStamp}], // eslint-disable-line camelcase
|
data: [{id: subscriptionId, current_period_end: currentPeriodEndTimeStamp}], // eslint-disable-line camelcase
|
||||||
},
|
},
|
||||||
@@ -3,12 +3,12 @@ import cc from 'coupon-code';
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
generateGroup,
|
generateGroup,
|
||||||
} from '../../../../../../helpers/api-unit.helper.js';
|
} from '../../../../../helpers/api-unit.helper.js';
|
||||||
import { model as User } from '../../../../../../../website/server/models/user';
|
import { model as User } from '../../../../../../website/server/models/user';
|
||||||
import { model as Coupon } from '../../../../../../../website/server/models/coupon';
|
import { model as Coupon } from '../../../../../../website/server/models/coupon';
|
||||||
import stripePayments from '../../../../../../../website/server/libs/stripePayments';
|
import stripePayments from '../../../../../../website/server/libs/payments/stripe';
|
||||||
import payments from '../../../../../../../website/server/libs/payments';
|
import payments from '../../../../../../website/server/libs/payments/payments';
|
||||||
import common from '../../../../../../../website/common';
|
import common from '../../../../../../website/common';
|
||||||
|
|
||||||
const i18n = common.i18n;
|
const i18n = common.i18n;
|
||||||
|
|
||||||
@@ -54,7 +54,7 @@ describe('checkout with subscription', () => {
|
|||||||
token = 'test-token';
|
token = 'test-token';
|
||||||
|
|
||||||
spy = sinon.stub(stripe.subscriptions, 'update');
|
spy = sinon.stub(stripe.subscriptions, 'update');
|
||||||
spy.returnsPromise().resolves;
|
spy.resolves;
|
||||||
|
|
||||||
stripeCreateCustomerSpy = sinon.stub(stripe.customers, 'create');
|
stripeCreateCustomerSpy = sinon.stub(stripe.customers, 'create');
|
||||||
let stripCustomerResponse = {
|
let stripCustomerResponse = {
|
||||||
@@ -63,10 +63,10 @@ describe('checkout with subscription', () => {
|
|||||||
data: [{id: subscriptionId}],
|
data: [{id: subscriptionId}],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
stripeCreateCustomerSpy.returnsPromise().resolves(stripCustomerResponse);
|
stripeCreateCustomerSpy.resolves(stripCustomerResponse);
|
||||||
|
|
||||||
stripePaymentsCreateSubSpy = sinon.stub(payments, 'createSubscription');
|
stripePaymentsCreateSubSpy = sinon.stub(payments, 'createSubscription');
|
||||||
stripePaymentsCreateSubSpy.returnsPromise().resolves({});
|
stripePaymentsCreateSubSpy.resolves({});
|
||||||
|
|
||||||
data.groupId = group._id;
|
data.groupId = group._id;
|
||||||
data.sub.quantity = 3;
|
data.sub.quantity = 3;
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
import stripeModule from 'stripe';
|
import stripeModule from 'stripe';
|
||||||
|
|
||||||
import { model as User } from '../../../../../../../website/server/models/user';
|
import { model as User } from '../../../../../../website/server/models/user';
|
||||||
import stripePayments from '../../../../../../../website/server/libs/stripePayments';
|
import stripePayments from '../../../../../../website/server/libs/payments/stripe';
|
||||||
import payments from '../../../../../../../website/server/libs/payments';
|
import payments from '../../../../../../website/server/libs/payments/payments';
|
||||||
import common from '../../../../../../../website/common';
|
import common from '../../../../../../website/common';
|
||||||
|
|
||||||
const i18n = common.i18n;
|
const i18n = common.i18n;
|
||||||
|
|
||||||
@@ -26,9 +26,9 @@ describe('checkout', () => {
|
|||||||
let stripCustomerResponse = {
|
let stripCustomerResponse = {
|
||||||
id: customerIdResponse,
|
id: customerIdResponse,
|
||||||
};
|
};
|
||||||
stripeChargeStub = sinon.stub(stripe.charges, 'create').returnsPromise().resolves(stripCustomerResponse);
|
stripeChargeStub = sinon.stub(stripe.charges, 'create').resolves(stripCustomerResponse);
|
||||||
paymentBuyGemsStub = sinon.stub(payments, 'buyGems').returnsPromise().resolves({});
|
paymentBuyGemsStub = sinon.stub(payments, 'buyGems').resolves({});
|
||||||
paymentCreateSubscritionStub = sinon.stub(payments, 'createSubscription').returnsPromise().resolves({});
|
paymentCreateSubscritionStub = sinon.stub(payments, 'createSubscription').resolves({});
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@@ -37,6 +37,22 @@ describe('checkout', () => {
|
|||||||
payments.createSubscription.restore();
|
payments.createSubscription.restore();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should error if there is no token', async () => {
|
||||||
|
await expect(stripePayments.checkout({
|
||||||
|
user,
|
||||||
|
gift,
|
||||||
|
groupId,
|
||||||
|
email,
|
||||||
|
headers,
|
||||||
|
coupon,
|
||||||
|
}, stripe))
|
||||||
|
.to.eventually.be.rejected.and.to.eql({
|
||||||
|
httpCode: 400,
|
||||||
|
message: 'Missing req.body.id',
|
||||||
|
name: 'BadRequest',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should error if gem amount is too low', async () => {
|
it('should error if gem amount is too low', async () => {
|
||||||
let receivingUser = new User();
|
let receivingUser = new User();
|
||||||
receivingUser.save();
|
receivingUser.save();
|
||||||
@@ -64,10 +80,9 @@ describe('checkout', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('should error if user cannot get gems', async () => {
|
it('should error if user cannot get gems', async () => {
|
||||||
gift = undefined;
|
gift = undefined;
|
||||||
sinon.stub(user, 'canGetGems').returnsPromise().resolves(false);
|
sinon.stub(user, 'canGetGems').resolves(false);
|
||||||
|
|
||||||
await expect(stripePayments.checkout({
|
await expect(stripePayments.checkout({
|
||||||
token,
|
token,
|
||||||
@@ -86,7 +101,7 @@ describe('checkout', () => {
|
|||||||
|
|
||||||
it('should purchase gems', async () => {
|
it('should purchase gems', async () => {
|
||||||
gift = undefined;
|
gift = undefined;
|
||||||
sinon.stub(user, 'canGetGems').returnsPromise().resolves(true);
|
sinon.stub(user, 'canGetGems').resolves(true);
|
||||||
|
|
||||||
await stripePayments.checkout({
|
await stripePayments.checkout({
|
||||||
token,
|
token,
|
||||||
@@ -2,10 +2,10 @@ import stripeModule from 'stripe';
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
generateGroup,
|
generateGroup,
|
||||||
} from '../../../../../../helpers/api-unit.helper.js';
|
} from '../../../../../helpers/api-unit.helper.js';
|
||||||
import { model as User } from '../../../../../../../website/server/models/user';
|
import { model as User } from '../../../../../../website/server/models/user';
|
||||||
import stripePayments from '../../../../../../../website/server/libs/stripePayments';
|
import stripePayments from '../../../../../../website/server/libs/payments/stripe';
|
||||||
import common from '../../../../../../../website/common';
|
import common from '../../../../../../website/common';
|
||||||
|
|
||||||
const i18n = common.i18n;
|
const i18n = common.i18n;
|
||||||
|
|
||||||
@@ -98,11 +98,11 @@ describe('edit subscription', () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
subscriptionId = 'subId';
|
subscriptionId = 'subId';
|
||||||
stripeListSubscriptionStub = sinon.stub(stripe.customers, 'listSubscriptions')
|
stripeListSubscriptionStub = sinon.stub(stripe.customers, 'listSubscriptions')
|
||||||
.returnsPromise().resolves({
|
.resolves({
|
||||||
data: [{id: subscriptionId}],
|
data: [{id: subscriptionId}],
|
||||||
});
|
});
|
||||||
|
|
||||||
stripeUpdateSubscriptionStub = sinon.stub(stripe.customers, 'updateSubscription').returnsPromise().resolves({});
|
stripeUpdateSubscriptionStub = sinon.stub(stripe.customers, 'updateSubscription').resolves({});
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@@ -2,12 +2,12 @@ import stripeModule from 'stripe';
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
generateGroup,
|
generateGroup,
|
||||||
} from '../../../../../../helpers/api-unit.helper.js';
|
} from '../../../../../helpers/api-unit.helper.js';
|
||||||
import { model as User } from '../../../../../../../website/server/models/user';
|
import { model as User } from '../../../../../../website/server/models/user';
|
||||||
import stripePayments from '../../../../../../../website/server/libs/stripePayments';
|
import stripePayments from '../../../../../../website/server/libs/payments/stripe';
|
||||||
import payments from '../../../../../../../website/server/libs/payments';
|
import payments from '../../../../../../website/server/libs/payments/payments';
|
||||||
import common from '../../../../../../../website/common';
|
import common from '../../../../../../website/common';
|
||||||
import logger from '../../../../../../../website/server/libs/logger';
|
import logger from '../../../../../../website/server/libs/logger';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
|
||||||
@@ -22,7 +22,7 @@ describe('Stripe - Webhooks', () => {
|
|||||||
const eventRetrieved = {type: eventType};
|
const eventRetrieved = {type: eventType};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
sinon.stub(stripe.events, 'retrieve').returnsPromise().resolves(eventRetrieved);
|
sinon.stub(stripe.events, 'retrieve').resolves(eventRetrieved);
|
||||||
sinon.stub(logger, 'error');
|
sinon.stub(logger, 'error');
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -52,8 +52,8 @@ describe('Stripe - Webhooks', () => {
|
|||||||
const eventType = 'customer.subscription.deleted';
|
const eventType = 'customer.subscription.deleted';
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
sinon.stub(stripe.customers, 'del').returnsPromise().resolves({});
|
sinon.stub(stripe.customers, 'del').resolves({});
|
||||||
sinon.stub(payments, 'cancelSubscription').returnsPromise().resolves({});
|
sinon.stub(payments, 'cancelSubscription').resolves({});
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@@ -62,7 +62,7 @@ describe('Stripe - Webhooks', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('does not do anything if event.request is null (subscription cancelled manually)', async () => {
|
it('does not do anything if event.request is null (subscription cancelled manually)', async () => {
|
||||||
sinon.stub(stripe.events, 'retrieve').returnsPromise().resolves({
|
sinon.stub(stripe.events, 'retrieve').resolves({
|
||||||
id: 123,
|
id: 123,
|
||||||
type: eventType,
|
type: eventType,
|
||||||
request: 123,
|
request: 123,
|
||||||
@@ -79,7 +79,7 @@ describe('Stripe - Webhooks', () => {
|
|||||||
describe('user subscription', () => {
|
describe('user subscription', () => {
|
||||||
it('throws an error if the user is not found', async () => {
|
it('throws an error if the user is not found', async () => {
|
||||||
const customerId = 456;
|
const customerId = 456;
|
||||||
sinon.stub(stripe.events, 'retrieve').returnsPromise().resolves({
|
sinon.stub(stripe.events, 'retrieve').resolves({
|
||||||
id: 123,
|
id: 123,
|
||||||
type: eventType,
|
type: eventType,
|
||||||
data: {
|
data: {
|
||||||
@@ -113,7 +113,7 @@ describe('Stripe - Webhooks', () => {
|
|||||||
subscriber.purchased.plan.paymentMethod = 'Stripe';
|
subscriber.purchased.plan.paymentMethod = 'Stripe';
|
||||||
await subscriber.save();
|
await subscriber.save();
|
||||||
|
|
||||||
sinon.stub(stripe.events, 'retrieve').returnsPromise().resolves({
|
sinon.stub(stripe.events, 'retrieve').resolves({
|
||||||
id: 123,
|
id: 123,
|
||||||
type: eventType,
|
type: eventType,
|
||||||
data: {
|
data: {
|
||||||
@@ -146,7 +146,7 @@ describe('Stripe - Webhooks', () => {
|
|||||||
describe('group plan subscription', () => {
|
describe('group plan subscription', () => {
|
||||||
it('throws an error if the group is not found', async () => {
|
it('throws an error if the group is not found', async () => {
|
||||||
const customerId = 456;
|
const customerId = 456;
|
||||||
sinon.stub(stripe.events, 'retrieve').returnsPromise().resolves({
|
sinon.stub(stripe.events, 'retrieve').resolves({
|
||||||
id: 123,
|
id: 123,
|
||||||
type: eventType,
|
type: eventType,
|
||||||
data: {
|
data: {
|
||||||
@@ -185,7 +185,7 @@ describe('Stripe - Webhooks', () => {
|
|||||||
subscriber.purchased.plan.paymentMethod = 'Stripe';
|
subscriber.purchased.plan.paymentMethod = 'Stripe';
|
||||||
await subscriber.save();
|
await subscriber.save();
|
||||||
|
|
||||||
sinon.stub(stripe.events, 'retrieve').returnsPromise().resolves({
|
sinon.stub(stripe.events, 'retrieve').resolves({
|
||||||
id: 123,
|
id: 123,
|
||||||
type: eventType,
|
type: eventType,
|
||||||
data: {
|
data: {
|
||||||
@@ -227,7 +227,7 @@ describe('Stripe - Webhooks', () => {
|
|||||||
subscriber.purchased.plan.paymentMethod = 'Stripe';
|
subscriber.purchased.plan.paymentMethod = 'Stripe';
|
||||||
await subscriber.save();
|
await subscriber.save();
|
||||||
|
|
||||||
sinon.stub(stripe.events, 'retrieve').returnsPromise().resolves({
|
sinon.stub(stripe.events, 'retrieve').resolves({
|
||||||
id: 123,
|
id: 123,
|
||||||
type: eventType,
|
type: eventType,
|
||||||
data: {
|
data: {
|
||||||
@@ -2,11 +2,11 @@ import stripeModule from 'stripe';
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
generateGroup,
|
generateGroup,
|
||||||
} from '../../../../../../helpers/api-unit.helper.js';
|
} from '../../../../../helpers/api-unit.helper.js';
|
||||||
import { model as User } from '../../../../../../../website/server/models/user';
|
import { model as User } from '../../../../../../website/server/models/user';
|
||||||
import { model as Group } from '../../../../../../../website/server/models/group';
|
import { model as Group } from '../../../../../../website/server/models/group';
|
||||||
import stripePayments from '../../../../../../../website/server/libs/stripePayments';
|
import stripePayments from '../../../../../../website/server/libs/payments/stripe';
|
||||||
import payments from '../../../../../../../website/server/libs/payments';
|
import payments from '../../../../../../website/server/libs/payments/payments';
|
||||||
|
|
||||||
describe('Stripe - Upgrade Group Plan', () => {
|
describe('Stripe - Upgrade Group Plan', () => {
|
||||||
const stripe = stripeModule('test');
|
const stripe = stripeModule('test');
|
||||||
@@ -38,7 +38,7 @@ describe('Stripe - Upgrade Group Plan', () => {
|
|||||||
await group.save();
|
await group.save();
|
||||||
|
|
||||||
spy = sinon.stub(stripe.subscriptions, 'update');
|
spy = sinon.stub(stripe.subscriptions, 'update');
|
||||||
spy.returnsPromise().resolves([]);
|
spy.resolves([]);
|
||||||
data.groupId = group._id;
|
data.groupId = group._id;
|
||||||
data.sub.quantity = 3;
|
data.sub.quantity = 3;
|
||||||
stripePayments.setStripeApi(stripe);
|
stripePayments.setStripeApi(stripe);
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { preenHistory } from '../../../../../website/server/libs/preening';
|
import { preenHistory } from '../../../../website/server/libs/preening';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import sinon from 'sinon'; // eslint-disable-line no-shadow
|
import sinon from 'sinon'; // eslint-disable-line no-shadow
|
||||||
import { generateHistory } from '../../../../helpers/api-unit.helper.js';
|
import { generateHistory } from '../../../helpers/api-unit.helper.js';
|
||||||
|
|
||||||
describe('preenHistory', () => {
|
describe('preenHistory', () => {
|
||||||
let clock;
|
let clock;
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
import { model as User } from '../../../../../website/server/models/user';
|
import { model as User } from '../../../../website/server/models/user';
|
||||||
import requireAgain from 'require-again';
|
import requireAgain from 'require-again';
|
||||||
import pushNotify from 'push-notify';
|
import apn from 'apn/mock';
|
||||||
import nconf from 'nconf';
|
import nconf from 'nconf';
|
||||||
import gcmLib from 'node-gcm'; // works with FCM notifications too
|
import gcmLib from 'node-gcm'; // works with FCM notifications too
|
||||||
|
|
||||||
describe('pushNotifications', () => {
|
describe('pushNotifications', () => {
|
||||||
let user;
|
let user;
|
||||||
let sendPushNotification;
|
let sendPushNotification;
|
||||||
let pathToPushNotifications = '../../../../../website/server/libs/pushNotifications';
|
let pathToPushNotifications = '../../../../website/server/libs/pushNotifications';
|
||||||
let fcmSendSpy;
|
let fcmSendSpy;
|
||||||
let apnSendSpy;
|
let apnSendSpy;
|
||||||
|
|
||||||
@@ -24,7 +24,7 @@ describe('pushNotifications', () => {
|
|||||||
|
|
||||||
sandbox.stub(gcmLib.Sender.prototype, 'send').callsFake(fcmSendSpy);
|
sandbox.stub(gcmLib.Sender.prototype, 'send').callsFake(fcmSendSpy);
|
||||||
|
|
||||||
sandbox.stub(pushNotify, 'apn').returns({
|
sandbox.stub(apn.Provider.prototype, 'send').returns({
|
||||||
on: () => null,
|
on: () => null,
|
||||||
send: apnSendSpy,
|
send: apnSendSpy,
|
||||||
});
|
});
|
||||||
@@ -104,10 +104,7 @@ describe('pushNotifications', () => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
sendPushNotification(user, details);
|
const expectedNotification = new apn.Notification({
|
||||||
expect(apnSendSpy).to.have.been.calledOnce;
|
|
||||||
expect(apnSendSpy).to.have.been.calledWithMatch({
|
|
||||||
token: '123',
|
|
||||||
alert: message,
|
alert: message,
|
||||||
sound: 'default',
|
sound: 'default',
|
||||||
category: 'fun',
|
category: 'fun',
|
||||||
@@ -117,6 +114,10 @@ describe('pushNotifications', () => {
|
|||||||
b: true,
|
b: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
sendPushNotification(user, details);
|
||||||
|
expect(apnSendSpy).to.have.been.calledOnce;
|
||||||
|
expect(apnSendSpy).to.have.been.calledWithMatch(expectedNotification, '123');
|
||||||
expect(fcmSendSpy).to.not.have.been.called;
|
expect(fcmSendSpy).to.not.have.been.called;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import setupNconf from '../../../../../website/server/libs/setupNconf';
|
import setupNconf from '../../../../website/server/libs/setupNconf';
|
||||||
|
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import nconf from 'nconf';
|
import nconf from 'nconf';
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
/* eslint-disable camelcase */
|
/* eslint-disable camelcase */
|
||||||
import { IncomingWebhook } from '@slack/client';
|
import { IncomingWebhook } from '@slack/client';
|
||||||
import requireAgain from 'require-again';
|
import requireAgain from 'require-again';
|
||||||
import slack from '../../../../../website/server/libs/slack';
|
import slack from '../../../../website/server/libs/slack';
|
||||||
import logger from '../../../../../website/server/libs/logger';
|
import logger from '../../../../website/server/libs/logger';
|
||||||
import { TAVERN_ID } from '../../../../../website/server/models/group';
|
import { TAVERN_ID } from '../../../../website/server/models/group';
|
||||||
import nconf from 'nconf';
|
import nconf from 'nconf';
|
||||||
|
import moment from 'moment';
|
||||||
|
|
||||||
describe('slack', () => {
|
describe('slack', () => {
|
||||||
describe('sendFlagNotification', () => {
|
describe('sendFlagNotification', () => {
|
||||||
@@ -45,17 +46,19 @@ describe('slack', () => {
|
|||||||
it('sends a slack webhook', () => {
|
it('sends a slack webhook', () => {
|
||||||
slack.sendFlagNotification(data);
|
slack.sendFlagNotification(data);
|
||||||
|
|
||||||
|
const timestamp = `${moment(data.message.timestamp).utc().format('YYYY-MM-DD HH:mm')} UTC`;
|
||||||
|
|
||||||
expect(IncomingWebhook.prototype.send).to.be.calledOnce;
|
expect(IncomingWebhook.prototype.send).to.be.calledOnce;
|
||||||
expect(IncomingWebhook.prototype.send).to.be.calledWith({
|
expect(IncomingWebhook.prototype.send).to.be.calledWith({
|
||||||
text: 'flagger (flagger-id) flagged a message (language: flagger-lang)',
|
text: 'flagger (flagger-id; language: flagger-lang) flagged a message',
|
||||||
attachments: [{
|
attachments: [{
|
||||||
fallback: 'Flag Message',
|
fallback: 'Flag Message',
|
||||||
color: 'danger',
|
color: 'danger',
|
||||||
author_name: 'Author - author@example.com - author-id',
|
author_name: `Author - author@example.com - author-id\n${timestamp}`,
|
||||||
title: 'Flag in Some group - (private guild)',
|
title: 'Flag in Some group - (private guild)',
|
||||||
title_link: undefined,
|
title_link: undefined,
|
||||||
text: 'some text',
|
text: 'some text',
|
||||||
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: [
|
||||||
'text',
|
'text',
|
||||||
],
|
],
|
||||||
@@ -97,17 +100,19 @@ describe('slack', () => {
|
|||||||
|
|
||||||
slack.sendFlagNotification(data);
|
slack.sendFlagNotification(data);
|
||||||
|
|
||||||
|
const timestamp = `${moment(data.message.timestamp).utc().format('YYYY-MM-DD HH:mm')} UTC`;
|
||||||
|
|
||||||
expect(IncomingWebhook.prototype.send).to.be.calledWithMatch({
|
expect(IncomingWebhook.prototype.send).to.be.calledWithMatch({
|
||||||
attachments: [sandbox.match({
|
attachments: [sandbox.match({
|
||||||
author_name: 'System Message',
|
author_name: `System Message\n${timestamp}`,
|
||||||
})],
|
})],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('noops if no flagging url is provided', () => {
|
it('noops if no flagging url is provided', () => {
|
||||||
sandbox.stub(nconf, 'get').withArgs('SLACK:FLAGGING_URL').returns('');
|
sandbox.stub(nconf, 'get').withArgs('SLACK_FLAGGING_URL').returns('');
|
||||||
sandbox.stub(logger, 'error');
|
sandbox.stub(logger, 'error');
|
||||||
let reRequiredSlack = requireAgain('../../../../../website/server/libs/slack');
|
let reRequiredSlack = requireAgain('../../../../website/server/libs/slack');
|
||||||
|
|
||||||
expect(logger.error).to.be.calledOnce;
|
expect(logger.error).to.be.calledOnce;
|
||||||
|
|
||||||
@@ -3,13 +3,13 @@ import {
|
|||||||
getTasks,
|
getTasks,
|
||||||
syncableAttrs,
|
syncableAttrs,
|
||||||
moveTask,
|
moveTask,
|
||||||
} from '../../../../../website/server/libs/taskManager';
|
} from '../../../../website/server/libs/taskManager';
|
||||||
import i18n from '../../../../../website/common/script/i18n';
|
import i18n from '../../../../website/common/script/i18n';
|
||||||
import {
|
import {
|
||||||
generateUser,
|
generateUser,
|
||||||
generateGroup,
|
generateGroup,
|
||||||
generateChallenge,
|
generateChallenge,
|
||||||
} from '../../../../helpers/api-unit.helper.js';
|
} from '../../../helpers/api-unit.helper.js';
|
||||||
|
|
||||||
describe('taskManager', () => {
|
describe('taskManager', () => {
|
||||||
let user, group, challenge;
|
let user, group, challenge;
|
||||||
@@ -178,4 +178,12 @@ describe('taskManager', () => {
|
|||||||
|
|
||||||
expect(order).to.eql(['task-id-2', 'task-id-1']);
|
expect(order).to.eql(['task-id-2', 'task-id-1']);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('moves tasks to a specified position out of length', async () => {
|
||||||
|
let order = ['task-id-1'];
|
||||||
|
|
||||||
|
moveTask(order, 'task-id-2', 2);
|
||||||
|
|
||||||
|
expect(order).to.eql(['task-id-1', 'task-id-2']);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
@@ -4,11 +4,19 @@ import {
|
|||||||
taskScoredWebhook,
|
taskScoredWebhook,
|
||||||
groupChatReceivedWebhook,
|
groupChatReceivedWebhook,
|
||||||
taskActivityWebhook,
|
taskActivityWebhook,
|
||||||
} from '../../../../../website/server/libs/webhook';
|
questActivityWebhook,
|
||||||
import { defer } from '../../../../helpers/api-unit.helper';
|
userActivityWebhook,
|
||||||
|
} from '../../../../website/server/libs/webhook';
|
||||||
|
import {
|
||||||
|
model as User,
|
||||||
|
} from '../../../../website/server/models/user';
|
||||||
|
import {
|
||||||
|
generateUser,
|
||||||
|
} from '../../../helpers/api-unit.helper.js';
|
||||||
|
import { defer } from '../../../helpers/api-unit.helper';
|
||||||
|
|
||||||
describe('webhooks', () => {
|
describe('webhooks', () => {
|
||||||
let webhooks;
|
let webhooks, user;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
sandbox.stub(got, 'post').returns(defer().promise);
|
sandbox.stub(got, 'post').returns(defer().promise);
|
||||||
@@ -23,6 +31,26 @@ describe('webhooks', () => {
|
|||||||
updated: true,
|
updated: true,
|
||||||
deleted: true,
|
deleted: true,
|
||||||
scored: true,
|
scored: true,
|
||||||
|
checklistScored: true,
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
id: 'questActivity',
|
||||||
|
url: 'http://quest-activity.com',
|
||||||
|
enabled: true,
|
||||||
|
type: 'questActivity',
|
||||||
|
options: {
|
||||||
|
questStarted: true,
|
||||||
|
questFinised: true,
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
id: 'userActivity',
|
||||||
|
url: 'http://user-activity.com',
|
||||||
|
enabled: true,
|
||||||
|
type: 'userActivity',
|
||||||
|
options: {
|
||||||
|
petHatched: true,
|
||||||
|
mountRaised: true,
|
||||||
|
leveledUp: true,
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
id: 'groupChatReceived',
|
id: 'groupChatReceived',
|
||||||
@@ -33,6 +61,9 @@ describe('webhooks', () => {
|
|||||||
groupId: 'group-id',
|
groupId: 'group-id',
|
||||||
},
|
},
|
||||||
}];
|
}];
|
||||||
|
|
||||||
|
user = generateUser();
|
||||||
|
user.webhooks = webhooks;
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@@ -57,7 +88,8 @@ describe('webhooks', () => {
|
|||||||
|
|
||||||
let body = { foo: 'bar' };
|
let body = { foo: 'bar' };
|
||||||
|
|
||||||
sendWebhook.send([{id: 'custom-webhook', url: 'http://custom-url.com', enabled: true, type: 'custom'}], body);
|
user.webhooks = [{id: 'custom-webhook', url: 'http://custom-url.com', enabled: true, type: 'custom'}];
|
||||||
|
sendWebhook.send(user, body);
|
||||||
|
|
||||||
expect(WebhookSender.defaultTransformData).to.be.calledOnce;
|
expect(WebhookSender.defaultTransformData).to.be.calledOnce;
|
||||||
expect(got.post).to.be.calledOnce;
|
expect(got.post).to.be.calledOnce;
|
||||||
@@ -67,6 +99,30 @@ describe('webhooks', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('adds default data (user and webhookType) to the body', () => {
|
||||||
|
let sendWebhook = new WebhookSender({
|
||||||
|
type: 'custom',
|
||||||
|
});
|
||||||
|
sandbox.spy(sendWebhook, 'attachDefaultData');
|
||||||
|
|
||||||
|
let body = { foo: 'bar' };
|
||||||
|
|
||||||
|
user.webhooks = [{id: 'custom-webhook', url: 'http://custom-url.com', enabled: true, type: 'custom'}];
|
||||||
|
sendWebhook.send(user, body);
|
||||||
|
|
||||||
|
expect(sendWebhook.attachDefaultData).to.be.calledOnce;
|
||||||
|
expect(got.post).to.be.calledOnce;
|
||||||
|
expect(got.post).to.be.calledWithMatch('http://custom-url.com', {
|
||||||
|
json: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(body).to.eql({
|
||||||
|
foo: 'bar',
|
||||||
|
user: {_id: user._id},
|
||||||
|
webhookType: 'custom',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('can pass in a data transformation function', () => {
|
it('can pass in a data transformation function', () => {
|
||||||
sandbox.spy(WebhookSender, 'defaultTransformData');
|
sandbox.spy(WebhookSender, 'defaultTransformData');
|
||||||
let sendWebhook = new WebhookSender({
|
let sendWebhook = new WebhookSender({
|
||||||
@@ -80,7 +136,8 @@ describe('webhooks', () => {
|
|||||||
|
|
||||||
let body = { foo: 'bar' };
|
let body = { foo: 'bar' };
|
||||||
|
|
||||||
sendWebhook.send([{id: 'custom-webhook', url: 'http://custom-url.com', enabled: true, type: 'custom'}], body);
|
user.webhooks = [{id: 'custom-webhook', url: 'http://custom-url.com', enabled: true, type: 'custom'}];
|
||||||
|
sendWebhook.send(user, body);
|
||||||
|
|
||||||
expect(WebhookSender.defaultTransformData).to.not.be.called;
|
expect(WebhookSender.defaultTransformData).to.not.be.called;
|
||||||
expect(got.post).to.be.calledOnce;
|
expect(got.post).to.be.calledOnce;
|
||||||
@@ -93,7 +150,7 @@ describe('webhooks', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('provieds a default filter function', () => {
|
it('provides a default filter function', () => {
|
||||||
sandbox.spy(WebhookSender, 'defaultWebhookFilter');
|
sandbox.spy(WebhookSender, 'defaultWebhookFilter');
|
||||||
let sendWebhook = new WebhookSender({
|
let sendWebhook = new WebhookSender({
|
||||||
type: 'custom',
|
type: 'custom',
|
||||||
@@ -101,7 +158,8 @@ describe('webhooks', () => {
|
|||||||
|
|
||||||
let body = { foo: 'bar' };
|
let body = { foo: 'bar' };
|
||||||
|
|
||||||
sendWebhook.send([{id: 'custom-webhook', url: 'http://custom-url.com', enabled: true, type: 'custom'}], body);
|
user.webhooks = [{id: 'custom-webhook', url: 'http://custom-url.com', enabled: true, type: 'custom'}];
|
||||||
|
sendWebhook.send(user, body);
|
||||||
|
|
||||||
expect(WebhookSender.defaultWebhookFilter).to.be.calledOnce;
|
expect(WebhookSender.defaultWebhookFilter).to.be.calledOnce;
|
||||||
});
|
});
|
||||||
@@ -117,7 +175,8 @@ describe('webhooks', () => {
|
|||||||
|
|
||||||
let body = { foo: 'bar' };
|
let body = { foo: 'bar' };
|
||||||
|
|
||||||
sendWebhook.send([{id: 'custom-webhook', url: 'http://custom-url.com', enabled: true, type: 'custom'}], body);
|
user.webhooks = [{id: 'custom-webhook', url: 'http://custom-url.com', enabled: true, type: 'custom'}];
|
||||||
|
sendWebhook.send(user, body);
|
||||||
|
|
||||||
expect(WebhookSender.defaultWebhookFilter).to.not.be.called;
|
expect(WebhookSender.defaultWebhookFilter).to.not.be.called;
|
||||||
expect(got.post).to.not.be.called;
|
expect(got.post).to.not.be.called;
|
||||||
@@ -134,10 +193,11 @@ describe('webhooks', () => {
|
|||||||
|
|
||||||
let body = { foo: 'bar' };
|
let body = { foo: 'bar' };
|
||||||
|
|
||||||
sendWebhook.send([
|
user.webhooks = [
|
||||||
{ id: 'custom-webhook', url: 'http://custom-url.com', enabled: true, type: 'custom', options: { foo: 'bar' }},
|
{ id: 'custom-webhook', url: 'http://custom-url.com', enabled: true, type: 'custom', options: { foo: 'bar' }},
|
||||||
{ id: 'other-custom-webhook', url: 'http://other-custom-url.com', enabled: true, type: 'custom', options: { foo: 'foo' }},
|
{ id: 'other-custom-webhook', url: 'http://other-custom-url.com', enabled: true, type: 'custom', options: { foo: 'foo' }},
|
||||||
], body);
|
];
|
||||||
|
sendWebhook.send(user, body);
|
||||||
|
|
||||||
expect(got.post).to.be.calledOnce;
|
expect(got.post).to.be.calledOnce;
|
||||||
expect(got.post).to.be.calledWithMatch('http://custom-url.com');
|
expect(got.post).to.be.calledWithMatch('http://custom-url.com');
|
||||||
@@ -150,7 +210,8 @@ describe('webhooks', () => {
|
|||||||
|
|
||||||
let body = { foo: 'bar' };
|
let body = { foo: 'bar' };
|
||||||
|
|
||||||
sendWebhook.send([{id: 'custom-webhook', url: 'http://custom-url.com', enabled: false, type: 'custom'}], body);
|
user.webhooks = [{id: 'custom-webhook', url: 'http://custom-url.com', enabled: false, type: 'custom'}];
|
||||||
|
sendWebhook.send(user, body);
|
||||||
|
|
||||||
expect(got.post).to.not.be.called;
|
expect(got.post).to.not.be.called;
|
||||||
});
|
});
|
||||||
@@ -162,7 +223,8 @@ describe('webhooks', () => {
|
|||||||
|
|
||||||
let body = { foo: 'bar' };
|
let body = { foo: 'bar' };
|
||||||
|
|
||||||
sendWebhook.send([{id: 'custom-webhook', url: 'httxp://custom-url!!', enabled: true, type: 'custom'}], body);
|
user.webhooks = [{id: 'custom-webhook', url: 'httxp://custom-url!!!', enabled: true, type: 'custom'}];
|
||||||
|
sendWebhook.send(user, body);
|
||||||
|
|
||||||
expect(got.post).to.not.be.called;
|
expect(got.post).to.not.be.called;
|
||||||
});
|
});
|
||||||
@@ -174,10 +236,30 @@ describe('webhooks', () => {
|
|||||||
|
|
||||||
let body = { foo: 'bar' };
|
let body = { foo: 'bar' };
|
||||||
|
|
||||||
sendWebhook.send([
|
user.webhooks = [
|
||||||
{ id: 'custom-webhook', url: 'http://custom-url.com', enabled: true, type: 'custom'},
|
{ id: 'custom-webhook', url: 'http://custom-url.com', enabled: true, type: 'custom'},
|
||||||
{ id: 'other-webhook', url: 'http://other-url.com', enabled: true, type: 'other'},
|
{ id: 'other-webhook', url: 'http://other-url.com', enabled: true, type: 'other'},
|
||||||
], body);
|
];
|
||||||
|
sendWebhook.send(user, body);
|
||||||
|
|
||||||
|
expect(got.post).to.be.calledOnce;
|
||||||
|
expect(got.post).to.be.calledWithMatch('http://custom-url.com', {
|
||||||
|
body,
|
||||||
|
json: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sends every type of activity to global webhooks', () => {
|
||||||
|
let sendWebhook = new WebhookSender({
|
||||||
|
type: 'custom',
|
||||||
|
});
|
||||||
|
|
||||||
|
let body = { foo: 'bar' };
|
||||||
|
|
||||||
|
user.webhooks = [
|
||||||
|
{ id: 'global-webhook', url: 'http://custom-url.com', enabled: true, type: 'globalActivity'},
|
||||||
|
];
|
||||||
|
sendWebhook.send(user, body);
|
||||||
|
|
||||||
expect(got.post).to.be.calledOnce;
|
expect(got.post).to.be.calledOnce;
|
||||||
expect(got.post).to.be.calledWithMatch('http://custom-url.com', {
|
expect(got.post).to.be.calledWithMatch('http://custom-url.com', {
|
||||||
@@ -193,10 +275,11 @@ describe('webhooks', () => {
|
|||||||
|
|
||||||
let body = { foo: 'bar' };
|
let body = { foo: 'bar' };
|
||||||
|
|
||||||
sendWebhook.send([
|
user.webhooks = [
|
||||||
{ id: 'custom-webhook', url: 'http://custom-url.com', enabled: true, type: 'custom'},
|
{ id: 'custom-webhook', url: 'http://custom-url.com', enabled: true, type: 'custom'},
|
||||||
{ id: 'other-custom-webhook', url: 'http://other-url.com', enabled: true, type: 'custom'},
|
{ id: 'other-custom-webhook', url: 'http://other-url.com', enabled: true, type: 'custom'},
|
||||||
], body);
|
];
|
||||||
|
sendWebhook.send(user, body);
|
||||||
|
|
||||||
expect(got.post).to.be.calledTwice;
|
expect(got.post).to.be.calledTwice;
|
||||||
expect(got.post).to.be.calledWithMatch('http://custom-url.com', {
|
expect(got.post).to.be.calledWithMatch('http://custom-url.com', {
|
||||||
@@ -216,7 +299,6 @@ describe('webhooks', () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
data = {
|
data = {
|
||||||
user: {
|
user: {
|
||||||
_id: 'user-id',
|
|
||||||
_tmp: {foo: 'bar'},
|
_tmp: {foo: 'bar'},
|
||||||
stats: {
|
stats: {
|
||||||
lvl: 5,
|
lvl: 5,
|
||||||
@@ -227,17 +309,6 @@ describe('webhooks', () => {
|
|||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
addComputedStatsToJSONObj () {
|
|
||||||
let mockStats = Object.assign({
|
|
||||||
maxHealth: 50,
|
|
||||||
maxMP: 103,
|
|
||||||
toNextLevel: 40,
|
|
||||||
}, this.stats);
|
|
||||||
|
|
||||||
delete mockStats.toJSON;
|
|
||||||
|
|
||||||
return mockStats;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
task: {
|
task: {
|
||||||
text: 'text',
|
text: 'text',
|
||||||
@@ -245,18 +316,66 @@ describe('webhooks', () => {
|
|||||||
direction: 'up',
|
direction: 'up',
|
||||||
delta: 176,
|
delta: 176,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let mockStats = Object.assign({
|
||||||
|
maxHealth: 50,
|
||||||
|
maxMP: 103,
|
||||||
|
toNextLevel: 40,
|
||||||
|
}, data.user.stats);
|
||||||
|
delete mockStats.toJSON;
|
||||||
|
|
||||||
|
sandbox.stub(User, 'addComputedStatsToJSONObj').returns(mockStats);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('sends task and stats data', () => {
|
it('sends task and stats data', () => {
|
||||||
taskScoredWebhook.send(webhooks, data);
|
taskScoredWebhook.send(user, data);
|
||||||
|
|
||||||
expect(got.post).to.be.calledOnce;
|
expect(got.post).to.be.calledOnce;
|
||||||
expect(got.post).to.be.calledWithMatch(webhooks[0].url, {
|
expect(got.post).to.be.calledWithMatch(webhooks[0].url, {
|
||||||
json: true,
|
json: true,
|
||||||
body: {
|
body: {
|
||||||
type: 'scored',
|
type: 'scored',
|
||||||
|
webhookType: 'taskActivity',
|
||||||
user: {
|
user: {
|
||||||
_id: 'user-id',
|
_id: user._id,
|
||||||
|
_tmp: {foo: 'bar'},
|
||||||
|
stats: {
|
||||||
|
lvl: 5,
|
||||||
|
int: 10,
|
||||||
|
str: 5,
|
||||||
|
exp: 423,
|
||||||
|
toNextLevel: 40,
|
||||||
|
maxHealth: 50,
|
||||||
|
maxMP: 103,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
task: {
|
||||||
|
text: 'text',
|
||||||
|
},
|
||||||
|
direction: 'up',
|
||||||
|
delta: 176,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sends task and stats data to globalActivity webhookd', () => {
|
||||||
|
user.webhooks = [{
|
||||||
|
id: 'globalActivity',
|
||||||
|
url: 'http://global-activity.com',
|
||||||
|
enabled: true,
|
||||||
|
type: 'globalActivity',
|
||||||
|
}];
|
||||||
|
|
||||||
|
taskScoredWebhook.send(user, data);
|
||||||
|
|
||||||
|
expect(got.post).to.be.calledOnce;
|
||||||
|
expect(got.post).to.be.calledWithMatch('http://global-activity.com', {
|
||||||
|
json: true,
|
||||||
|
body: {
|
||||||
|
type: 'scored',
|
||||||
|
webhookType: 'taskActivity',
|
||||||
|
user: {
|
||||||
|
_id: user._id,
|
||||||
_tmp: {foo: 'bar'},
|
_tmp: {foo: 'bar'},
|
||||||
stats: {
|
stats: {
|
||||||
lvl: 5,
|
lvl: 5,
|
||||||
@@ -280,7 +399,7 @@ describe('webhooks', () => {
|
|||||||
it('does not send task scored data if scored option is not true', () => {
|
it('does not send task scored data if scored option is not true', () => {
|
||||||
webhooks[0].options.scored = false;
|
webhooks[0].options.scored = false;
|
||||||
|
|
||||||
taskScoredWebhook.send(webhooks, data);
|
taskScoredWebhook.send(user, data);
|
||||||
|
|
||||||
expect(got.post).to.not.be.called;
|
expect(got.post).to.not.be.called;
|
||||||
});
|
});
|
||||||
@@ -301,13 +420,17 @@ describe('webhooks', () => {
|
|||||||
it(`sends ${type} tasks`, () => {
|
it(`sends ${type} tasks`, () => {
|
||||||
data.type = type;
|
data.type = type;
|
||||||
|
|
||||||
taskActivityWebhook.send(webhooks, data);
|
taskActivityWebhook.send(user, data);
|
||||||
|
|
||||||
expect(got.post).to.be.calledOnce;
|
expect(got.post).to.be.calledOnce;
|
||||||
expect(got.post).to.be.calledWithMatch(webhooks[0].url, {
|
expect(got.post).to.be.calledWithMatch(webhooks[0].url, {
|
||||||
json: true,
|
json: true,
|
||||||
body: {
|
body: {
|
||||||
type,
|
type,
|
||||||
|
webhookType: 'taskActivity',
|
||||||
|
user: {
|
||||||
|
_id: user._id,
|
||||||
|
},
|
||||||
task: data.task,
|
task: data.task,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -317,7 +440,142 @@ describe('webhooks', () => {
|
|||||||
data.type = type;
|
data.type = type;
|
||||||
webhooks[0].options[type] = false;
|
webhooks[0].options[type] = false;
|
||||||
|
|
||||||
taskActivityWebhook.send(webhooks, data);
|
taskActivityWebhook.send(user, data);
|
||||||
|
|
||||||
|
expect(got.post).to.not.be.called;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('checklistScored', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
data = {
|
||||||
|
task: {
|
||||||
|
text: 'text',
|
||||||
|
},
|
||||||
|
item: {
|
||||||
|
text: 'item-text',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sends \'checklistScored\' tasks', () => {
|
||||||
|
data.type = 'checklistScored';
|
||||||
|
|
||||||
|
taskActivityWebhook.send(user, data);
|
||||||
|
|
||||||
|
expect(got.post).to.be.calledOnce;
|
||||||
|
expect(got.post).to.be.calledWithMatch(webhooks[0].url, {
|
||||||
|
json: true,
|
||||||
|
body: {
|
||||||
|
webhookType: 'taskActivity',
|
||||||
|
user: {
|
||||||
|
_id: user._id,
|
||||||
|
},
|
||||||
|
type: data.type,
|
||||||
|
task: data.task,
|
||||||
|
item: data.item,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not send task \'checklistScored\' data if \'checklistScored\' option is not true', () => {
|
||||||
|
data.type = 'checklistScored';
|
||||||
|
webhooks[0].options.checklistScored = false;
|
||||||
|
|
||||||
|
taskActivityWebhook.send(user, data);
|
||||||
|
|
||||||
|
expect(got.post).to.not.be.called;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('userActivityWebhook', () => {
|
||||||
|
let data;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
data = {
|
||||||
|
something: true,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
['petHatched', 'mountRaised', 'leveledUp'].forEach((type) => {
|
||||||
|
it(`sends ${type} webhooks`, () => {
|
||||||
|
data.type = type;
|
||||||
|
|
||||||
|
userActivityWebhook.send(user, data);
|
||||||
|
|
||||||
|
expect(got.post).to.be.calledOnce;
|
||||||
|
expect(got.post).to.be.calledWithMatch(webhooks[2].url, {
|
||||||
|
json: true,
|
||||||
|
body: {
|
||||||
|
type,
|
||||||
|
webhookType: 'userActivity',
|
||||||
|
user: {
|
||||||
|
_id: user._id,
|
||||||
|
},
|
||||||
|
something: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`does not send webhook ${type} data if ${type} option is not true`, () => {
|
||||||
|
data.type = type;
|
||||||
|
webhooks[2].options[type] = false;
|
||||||
|
|
||||||
|
userActivityWebhook.send(user, data);
|
||||||
|
|
||||||
|
expect(got.post).to.not.be.called;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('questActivityWebhook', () => {
|
||||||
|
let data;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
data = {
|
||||||
|
group: {
|
||||||
|
id: 'group-id',
|
||||||
|
name: 'some group',
|
||||||
|
otherData: 'foo',
|
||||||
|
},
|
||||||
|
quest: {
|
||||||
|
key: 'some-key',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
['questStarted', 'questFinised'].forEach((type) => {
|
||||||
|
it(`sends ${type} webhooks`, () => {
|
||||||
|
data.type = type;
|
||||||
|
|
||||||
|
questActivityWebhook.send(user, data);
|
||||||
|
|
||||||
|
expect(got.post).to.be.calledOnce;
|
||||||
|
expect(got.post).to.be.calledWithMatch(webhooks[1].url, {
|
||||||
|
json: true,
|
||||||
|
body: {
|
||||||
|
type,
|
||||||
|
webhookType: 'questActivity',
|
||||||
|
user: {
|
||||||
|
_id: user._id,
|
||||||
|
},
|
||||||
|
group: {
|
||||||
|
id: 'group-id',
|
||||||
|
name: 'some group',
|
||||||
|
},
|
||||||
|
quest: {
|
||||||
|
key: 'some-key',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`does not send webhook ${type} data if ${type} option is not true`, () => {
|
||||||
|
data.type = type;
|
||||||
|
webhooks[1].options[type] = false;
|
||||||
|
|
||||||
|
userActivityWebhook.send(user, data);
|
||||||
|
|
||||||
expect(got.post).to.not.be.called;
|
expect(got.post).to.not.be.called;
|
||||||
});
|
});
|
||||||
@@ -338,12 +596,16 @@ describe('webhooks', () => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
groupChatReceivedWebhook.send(webhooks, data);
|
groupChatReceivedWebhook.send(user, data);
|
||||||
|
|
||||||
expect(got.post).to.be.calledOnce;
|
expect(got.post).to.be.calledOnce;
|
||||||
expect(got.post).to.be.calledWithMatch(webhooks[webhooks.length - 1].url, {
|
expect(got.post).to.be.calledWithMatch(webhooks[webhooks.length - 1].url, {
|
||||||
json: true,
|
json: true,
|
||||||
body: {
|
body: {
|
||||||
|
webhookType: 'groupChatReceived',
|
||||||
|
user: {
|
||||||
|
_id: user._id,
|
||||||
|
},
|
||||||
group: {
|
group: {
|
||||||
id: 'group-id',
|
id: 'group-id',
|
||||||
name: 'some group',
|
name: 'some group',
|
||||||
@@ -369,7 +631,7 @@ describe('webhooks', () => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
groupChatReceivedWebhook.send(webhooks, data);
|
groupChatReceivedWebhook.send(user, data);
|
||||||
|
|
||||||
expect(got.post).to.not.be.called;
|
expect(got.post).to.not.be.called;
|
||||||
});
|
});
|
||||||
@@ -3,14 +3,14 @@ import {
|
|||||||
generateRes,
|
generateRes,
|
||||||
generateReq,
|
generateReq,
|
||||||
generateNext,
|
generateNext,
|
||||||
} from '../../../../helpers/api-unit.helper';
|
} from '../../../helpers/api-unit.helper';
|
||||||
import analyticsService from '../../../../../website/server/libs/analyticsService';
|
import analyticsService from '../../../../website/server/libs/analyticsService';
|
||||||
import nconf from 'nconf';
|
import nconf from 'nconf';
|
||||||
import requireAgain from 'require-again';
|
import requireAgain from 'require-again';
|
||||||
|
|
||||||
describe('analytics middleware', () => {
|
describe('analytics middleware', () => {
|
||||||
let res, req, next;
|
let res, req, next;
|
||||||
let pathToAnalyticsMiddleware = '../../../../../website/server/middlewares/analytics';
|
let pathToAnalyticsMiddleware = '../../../../website/server/middlewares/analytics';
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
res = generateRes();
|
res = generateRes();
|
||||||
40
test/api/unit/middlewares/auth.test.js
Normal file
40
test/api/unit/middlewares/auth.test.js
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import {
|
||||||
|
generateRes,
|
||||||
|
generateReq,
|
||||||
|
} from '../../../helpers/api-unit.helper';
|
||||||
|
import { authWithHeaders as authWithHeadersFactory } from '../../../../website/server/middlewares/auth';
|
||||||
|
|
||||||
|
describe('auth middleware', () => {
|
||||||
|
let res, req, user;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
res = generateRes();
|
||||||
|
req = generateReq();
|
||||||
|
user = await res.locals.user.save();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('auth with headers', () => {
|
||||||
|
it('allows to specify a list of user field that we do not want to load', (done) => {
|
||||||
|
const authWithHeaders = authWithHeadersFactory({
|
||||||
|
userFieldsToExclude: ['items', 'flags', 'auth.timestamps'],
|
||||||
|
});
|
||||||
|
|
||||||
|
req.headers['x-api-user'] = user._id;
|
||||||
|
req.headers['x-api-key'] = user.apiToken;
|
||||||
|
|
||||||
|
authWithHeaders(req, res, (err) => {
|
||||||
|
if (err) return done(err);
|
||||||
|
|
||||||
|
const userToJSON = res.locals.user.toJSON();
|
||||||
|
expect(userToJSON.items).to.not.exist;
|
||||||
|
expect(userToJSON.flags).to.not.exist;
|
||||||
|
expect(userToJSON.auth.timestamps).to.not.exist;
|
||||||
|
expect(userToJSON.auth).to.exist;
|
||||||
|
expect(userToJSON.notifications).to.exist;
|
||||||
|
expect(userToJSON.preferences).to.exist;
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user