mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-17 06:37:23 +01:00
Compare commits
459 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d062fd8507 | ||
|
|
ceb10137a2 | ||
|
|
58330a4d01 | ||
|
|
678f48fde9 | ||
|
|
f269d381da | ||
|
|
3483f69559 | ||
|
|
8bbe9ac36e | ||
|
|
75b93e6ec4 | ||
|
|
f7b298d506 | ||
|
|
4bd2932955 | ||
|
|
7060f5941d | ||
|
|
21379ee357 | ||
|
|
cb46cd8eeb | ||
|
|
d9d02ca81d | ||
|
|
50c216eb41 | ||
|
|
6bdf8fdabc | ||
|
|
3db304b6bf | ||
|
|
ea85a8a3a8 | ||
|
|
e811a2700e | ||
|
|
83b573dcfc | ||
|
|
c4fa9426b3 | ||
|
|
042e5a8d63 | ||
|
|
f7ce269f3c | ||
|
|
c084f8a2b9 | ||
|
|
c1c42e17b8 | ||
|
|
306a782e7a | ||
|
|
20178c0722 | ||
|
|
256e2e809c | ||
|
|
592345e22c | ||
|
|
292b2acb1e | ||
|
|
977f9d5174 | ||
|
|
85644fdc1b | ||
|
|
138b5c4bdb | ||
|
|
52edb8a8da | ||
|
|
1999e1098e | ||
|
|
4d3a0c0571 | ||
|
|
706de95458 | ||
|
|
e3c1eaa9d2 | ||
|
|
17c0f795cc | ||
|
|
cb5ac9014e | ||
|
|
0be681b7a2 | ||
|
|
5360f9e587 | ||
|
|
4553a411f6 | ||
|
|
613f51b08d | ||
|
|
2292ba2694 | ||
|
|
befacca457 | ||
|
|
5cd30b430d | ||
|
|
c5d9ee1e0a | ||
|
|
234328f2ba | ||
|
|
029afa197e | ||
|
|
05b35c5147 | ||
|
|
c9427ad34c | ||
|
|
d6c62262f1 | ||
|
|
ec1d378504 | ||
|
|
3bb88f450a | ||
|
|
97a38e68c5 | ||
|
|
be948a1bf2 | ||
|
|
018976a723 | ||
|
|
00e5896ac6 | ||
|
|
36bc693545 | ||
|
|
f27706cb4b | ||
|
|
f6f99ec57e | ||
|
|
c852d9d581 | ||
|
|
4a78514308 | ||
|
|
265b48752d | ||
|
|
db0b0d6b6d | ||
|
|
20f1087552 | ||
|
|
2e9bc2c31c | ||
|
|
b606dd1c40 | ||
|
|
db1c2fd5a2 | ||
|
|
de1e477ce2 | ||
|
|
67318177a2 | ||
|
|
cd1be828ca | ||
|
|
9ffebc10a7 | ||
|
|
5cd11ed343 | ||
|
|
9de118f0d9 | ||
|
|
5fbec4069e | ||
|
|
a0f10cbf4b | ||
|
|
0e069e78d5 | ||
|
|
dea847ba1a | ||
|
|
46ed1813c6 | ||
|
|
e9750353a7 | ||
|
|
05ea2c1ce6 | ||
|
|
74d1c7763e | ||
|
|
6f034bb5dd | ||
|
|
aeb8d4f500 | ||
|
|
58d910fe62 | ||
|
|
71f2f31606 | ||
|
|
feae40cf0a | ||
|
|
0d3fe53155 | ||
|
|
c3a3c1514a | ||
|
|
cc532fa993 | ||
|
|
ba66a1c098 | ||
|
|
b4d5c634b3 | ||
|
|
216006beab | ||
|
|
7c236e7e0e | ||
|
|
0221d2d7f9 | ||
|
|
567eb1d98b | ||
|
|
3cf533f261 | ||
|
|
c30c51f386 | ||
|
|
2de794c32b | ||
|
|
7d000d2cf6 | ||
|
|
280b720c13 | ||
|
|
9e1f7f3811 | ||
|
|
fefaed368a | ||
|
|
6f370395ea | ||
|
|
65566f7607 | ||
|
|
c0117706e4 | ||
|
|
f267456a30 | ||
|
|
228b724d52 | ||
|
|
19b09b4894 | ||
|
|
f49d21d7b4 | ||
|
|
c08c0685f3 | ||
|
|
59bfe66c94 | ||
|
|
023fd6e6b0 | ||
|
|
7ee2f90f37 | ||
|
|
46709ddadd | ||
|
|
a3ee09e764 | ||
|
|
4127f36c02 | ||
|
|
dad924ac7d | ||
|
|
547c87dee7 | ||
|
|
99a2013767 | ||
|
|
a5e0e171cc | ||
|
|
1d93943458 | ||
|
|
7f2719a75c | ||
|
|
8a9ed04f5e | ||
|
|
4e66c038a6 | ||
|
|
bd9618e1c6 | ||
|
|
342034a80d | ||
|
|
f2aed3cb27 | ||
|
|
4f5f15d162 | ||
|
|
b5c1b78789 | ||
|
|
f2ace5bb63 | ||
|
|
72b8ba2d5c | ||
|
|
8555e4bea8 | ||
|
|
4de5b4253b | ||
|
|
410004f8c6 | ||
|
|
76c7e15497 | ||
|
|
e6f605f23a | ||
|
|
0af1203832 | ||
|
|
1de379a2c3 | ||
|
|
388861b503 | ||
|
|
f52806ed69 | ||
|
|
d89b9e08af | ||
|
|
f8a99bd127 | ||
|
|
a82b60f144 | ||
|
|
65e71140ee | ||
|
|
f192ca4c6f | ||
|
|
36b09d40b9 | ||
|
|
4fdd754b31 | ||
|
|
36ff063991 | ||
|
|
20dcb1cb8a | ||
|
|
3a1527073f | ||
|
|
1292f9a3d5 | ||
|
|
e7418472f6 | ||
|
|
850f332ddc | ||
|
|
59fb32ea2e | ||
|
|
727cdc9402 | ||
|
|
638c9dee89 | ||
|
|
168ed02226 | ||
|
|
aa253cf72e | ||
|
|
247742c60d | ||
|
|
7d89deb094 | ||
|
|
c128b701fa | ||
|
|
2adac35e31 | ||
|
|
f01e13ffc0 | ||
|
|
4e9d631b71 | ||
|
|
f1de7c02e9 | ||
|
|
15d4f7d6ab | ||
|
|
76222ac344 | ||
|
|
2659a4117b | ||
|
|
a0ee73e944 | ||
|
|
6174624b89 | ||
|
|
c8b6e8ea7c | ||
|
|
409b5d5965 | ||
|
|
e7209511ca | ||
|
|
8c76ccd39b | ||
|
|
399c91ccab | ||
|
|
4bbebdd237 | ||
|
|
4f305bd505 | ||
|
|
024feaa2f4 | ||
|
|
d2dc8f1856 | ||
|
|
364ed8dbab | ||
|
|
73328b6dab | ||
|
|
e3a08c1905 | ||
|
|
dad0eea9e0 | ||
|
|
76849cdcaa | ||
|
|
eb6ac42717 | ||
|
|
d44a298e2d | ||
|
|
864ca91144 | ||
|
|
78816dd4cb | ||
|
|
6a99daebac | ||
|
|
c9ee6c7f73 | ||
|
|
ffc4618657 | ||
|
|
30fde273b8 | ||
|
|
38573ad357 | ||
|
|
c23180e6eb | ||
|
|
f7e2a0464f | ||
|
|
6994c6769a | ||
|
|
984e7f8005 | ||
|
|
e2f4b0e3dc | ||
|
|
369702884a | ||
|
|
d438990d18 | ||
|
|
7d3213fd66 | ||
|
|
bde4eafc05 | ||
|
|
5a2ba27808 | ||
|
|
024268a51e | ||
|
|
f7281e71e8 | ||
|
|
f3712c0641 | ||
|
|
7df10d51b0 | ||
|
|
91438aff90 | ||
|
|
0c3f40716b | ||
|
|
ec306b614a | ||
|
|
a6d8beff9d | ||
|
|
bebf03ee91 | ||
|
|
2ea35c673a | ||
|
|
90d15b18f8 | ||
|
|
91ed55cf66 | ||
|
|
93aa37a164 | ||
|
|
4275da0a2e | ||
|
|
f782687609 | ||
|
|
164fb69108 | ||
|
|
b1678e1769 | ||
|
|
7f8851c72b | ||
|
|
d9f48dcbb0 | ||
|
|
bd6f901ccf | ||
|
|
884bf02961 | ||
|
|
4aad44e29e | ||
|
|
b40ee88165 | ||
|
|
5d90aff51b | ||
|
|
9e36a531ea | ||
|
|
f9b40a699a | ||
|
|
33380f63f6 | ||
|
|
2f010e4689 | ||
|
|
fbda3a87ef | ||
|
|
8a2e6a98c2 | ||
|
|
7d42e8fc71 | ||
|
|
0442b87608 | ||
|
|
c1d1a3e14e | ||
|
|
dd5a9aa6cc | ||
|
|
0bb6e5f3fc | ||
|
|
ad0a51167d | ||
|
|
b7f1001b1a | ||
|
|
81ea1a0f9e | ||
|
|
a466d20935 | ||
|
|
635c0cf3d1 | ||
|
|
69347df050 | ||
|
|
9cad5525e6 | ||
|
|
b1e6aceffe | ||
|
|
17068875f4 | ||
|
|
342fc2e344 | ||
|
|
b8b1557e49 | ||
|
|
f90ef04e83 | ||
|
|
d0561512de | ||
|
|
b84c672f33 | ||
|
|
aafbb889be | ||
|
|
8bfafa6df0 | ||
|
|
13865bcf49 | ||
|
|
78a99bf314 | ||
|
|
38edc5b416 | ||
|
|
f9ca69196a | ||
|
|
0e77df6e7b | ||
|
|
91b6d3db02 | ||
|
|
d16ce1ce48 | ||
|
|
564c366bfb | ||
|
|
c8c65a4f4f | ||
|
|
8d168a0318 | ||
|
|
a3dd2f497e | ||
|
|
a4feae4dbb | ||
|
|
d8620e1636 | ||
|
|
8e6f4a15a7 | ||
|
|
7c516b7cbb | ||
|
|
46d96b444b | ||
|
|
60c9434b14 | ||
|
|
c3901e8615 | ||
|
|
d166de8ad0 | ||
|
|
82e9afe9ce | ||
|
|
999202a8a5 | ||
|
|
8b53adfcb1 | ||
|
|
c23062f87e | ||
|
|
ec98541df6 | ||
|
|
4fed13afdd | ||
|
|
866b28ec15 | ||
|
|
bdf4a69eaf | ||
|
|
4c121fba19 | ||
|
|
0ecb95a294 | ||
|
|
ea7c07e21d | ||
|
|
c4463f991b | ||
|
|
96ce948e1a | ||
|
|
6d06685dfa | ||
|
|
32b6566e37 | ||
|
|
ca3b4cd8ae | ||
|
|
c90b4b488e | ||
|
|
8c203637d7 | ||
|
|
58f72b7eaa | ||
|
|
f0bbe84bd1 | ||
|
|
038e3f3235 | ||
|
|
1a7c8c1f87 | ||
|
|
c73d6154a8 | ||
|
|
e8b77ad2b2 | ||
|
|
ea18489991 | ||
|
|
5eb1b6684e | ||
|
|
a2f77eeba2 | ||
|
|
d4198f8913 | ||
|
|
565d50dd99 | ||
|
|
be1754ab07 | ||
|
|
831b122ce2 | ||
|
|
03088f1d9f | ||
|
|
1d7b733759 | ||
|
|
d170f0b1bd | ||
|
|
4846bc5769 | ||
|
|
30f514e46f | ||
|
|
6aad018eb2 | ||
|
|
f2d9bdf7ae | ||
|
|
daf9421f4f | ||
|
|
2d7e280598 | ||
|
|
d1de41290d | ||
|
|
b2225f05e5 | ||
|
|
842fbe42a8 | ||
|
|
5eadf9e486 | ||
|
|
0fd85c0d60 | ||
|
|
68ad3e2d4a | ||
|
|
de947f8069 | ||
|
|
d541e3aa31 | ||
|
|
b0eda344f1 | ||
|
|
02708a7b10 | ||
|
|
fd9f3a32c4 | ||
|
|
6e0341a4ff | ||
|
|
625077fc1a | ||
|
|
9d456e934c | ||
|
|
771d8f492a | ||
|
|
f3fab88f0b | ||
|
|
207e3476e6 | ||
|
|
6956f5345e | ||
|
|
0ec293bd15 | ||
|
|
cb00ecc0be | ||
|
|
94ef4f80cc | ||
|
|
814b163e1a | ||
|
|
421bdce38b | ||
|
|
624566ecec | ||
|
|
77ff91868e | ||
|
|
cc68abb67e | ||
|
|
b48be4e619 | ||
|
|
7a543d07a4 | ||
|
|
59f490d178 | ||
|
|
ae64ef94ae | ||
|
|
2335e22a0c | ||
|
|
910154b3ed | ||
|
|
31e36339c4 | ||
|
|
07fe1df024 | ||
|
|
258742f6b7 | ||
|
|
9115be68b2 | ||
|
|
d9d7c69432 | ||
|
|
03d6c459bf | ||
|
|
12cefe4e9f | ||
|
|
21ad808cc1 | ||
|
|
db9befde17 | ||
|
|
01af658c2d | ||
|
|
52738575fa | ||
|
|
cc9bca5f63 | ||
|
|
05d75a4d5c | ||
|
|
8c68f450c6 | ||
|
|
164177f010 | ||
|
|
fc69a3a960 | ||
|
|
797adbb1dc | ||
|
|
3dc20a9832 | ||
|
|
cfa433aa75 | ||
|
|
9bc2d22d30 | ||
|
|
4909f67ded | ||
|
|
8bc6534ff5 | ||
|
|
83bd4dcf60 | ||
|
|
a8ebd04ac8 | ||
|
|
e0d499abab | ||
|
|
f67e065de2 | ||
|
|
283403d6c8 | ||
|
|
55c7d6a191 | ||
|
|
a5011f000e | ||
|
|
c5633e2074 | ||
|
|
939712ad1f | ||
|
|
09490551d4 | ||
|
|
92b02295b5 | ||
|
|
767763fbf6 | ||
|
|
237e0df611 | ||
|
|
ca903f0dc3 | ||
|
|
bde41699ee | ||
|
|
d0d4b47c47 | ||
|
|
c616346233 | ||
|
|
311d3a256a | ||
|
|
af6f3f9656 | ||
|
|
15681fedcc | ||
|
|
bf12f8aa71 | ||
|
|
41ee72d407 | ||
|
|
9a5f6d4ad6 | ||
|
|
d19237cdbe | ||
|
|
18bd3f8c54 | ||
|
|
8b65ce3053 | ||
|
|
38b894db56 | ||
|
|
61db283473 | ||
|
|
d70d39cc49 | ||
|
|
c26b884bc7 | ||
|
|
11c8f2a775 | ||
|
|
1082359f2c | ||
|
|
6486862242 | ||
|
|
f68cc569d6 | ||
|
|
b10751e874 | ||
|
|
b75c57f130 | ||
|
|
28c93ea869 | ||
|
|
7f630f2b86 | ||
|
|
1a8f591251 | ||
|
|
be60fb0635 | ||
|
|
03a1d61c08 | ||
|
|
0767dc97b7 | ||
|
|
4978a62829 | ||
|
|
0a35e63897 | ||
|
|
03b3e79ea0 | ||
|
|
dc8598ae81 | ||
|
|
3629f7f8a5 | ||
|
|
8805f81b96 | ||
|
|
207dbf35d6 | ||
|
|
448a953147 | ||
|
|
4fb1ff2baa | ||
|
|
be64274be4 | ||
|
|
390970a73a | ||
|
|
0cb254d5fc | ||
|
|
98c019a0b6 | ||
|
|
ef02e59590 | ||
|
|
93befcebcc | ||
|
|
68a042cdb9 | ||
|
|
30954fe7c5 | ||
|
|
44f23a7675 | ||
|
|
705a78e835 | ||
|
|
6d0df78441 | ||
|
|
6f0d0b1fb3 | ||
|
|
dfcd32d54a | ||
|
|
c24cbdc987 | ||
|
|
68d8c0de51 | ||
|
|
a7b0fa195f | ||
|
|
ab65aa692f | ||
|
|
198d4df02a | ||
|
|
6fa2f643fd | ||
|
|
4d39861b51 | ||
|
|
d4c99b6db6 | ||
|
|
a79dc90a45 | ||
|
|
daa6bd0315 | ||
|
|
99f2373214 | ||
|
|
374d528647 | ||
|
|
8550ca4d29 | ||
|
|
79f6b59f6e | ||
|
|
e37ad420ce | ||
|
|
2f9ff92cbd | ||
|
|
56479e7fbd | ||
|
|
66e4849553 | ||
|
|
9e29b44ad9 | ||
|
|
605488dd47 | ||
|
|
6c16b4b77e | ||
|
|
80205dcc67 | ||
|
|
65d5bf69f6 | ||
|
|
20792f5455 | ||
|
|
6fd509df13 |
@@ -8,16 +8,13 @@ dist/
|
|||||||
dist-client/
|
dist-client/
|
||||||
|
|
||||||
# Not linted
|
# Not linted
|
||||||
migrations/*
|
|
||||||
website/client-old/
|
website/client-old/
|
||||||
scripts/*
|
|
||||||
test/server_side/**/*
|
|
||||||
test/client-old/spec/**/*
|
test/client-old/spec/**/*
|
||||||
|
|
||||||
# Temporarilly disabled. These should be removed when the linting errors are fixed TODO
|
# Temporarilly disabled. These should be removed when the linting errors are fixed TODO
|
||||||
website/common/script/content/index.js
|
migrations/*
|
||||||
|
scripts/*
|
||||||
website/common/browserify.js
|
website/common/browserify.js
|
||||||
test/content/**/*
|
|
||||||
Gruntfile.js
|
Gruntfile.js
|
||||||
gulpfile.js
|
gulpfile.js
|
||||||
gulp
|
gulp
|
||||||
2
.github/CONTRIBUTING.md
vendored
2
.github/CONTRIBUTING.md
vendored
@@ -1,6 +1,6 @@
|
|||||||
# Reporting Bugs
|
# Reporting Bugs
|
||||||
|
|
||||||
[Please see these instructions for reporting bugs](https://github.com/HabitRPG/habitrpg/issues/2760)
|
[Please see these instructions for reporting bugs](https://github.com/HabitRPG/habitica/issues/2760)
|
||||||
|
|
||||||
# Pull Request
|
# Pull Request
|
||||||
|
|
||||||
|
|||||||
14
.github/ISSUE_TEMPLATE.md
vendored
14
.github/ISSUE_TEMPLATE.md
vendored
@@ -1,21 +1,19 @@
|
|||||||
[//]: # (Before logging this issue, look through common problems at https://github.com/HabitRPG/habitrpg/issues If you find your issue there, read at least the first post to see if there is a workaround for you)
|
[//]: # (Before logging this issue, please post to the Report a Bug guild from the Habitica website's Help menu. Most bugs can be handled quickly there. If a GitHub issue is needed, you will be advised of that by a moderator or staff member -- a player with a dark blue or purple name. It is recommended that you don't create a new issue unless advised to.)
|
||||||
|
|
||||||
[//]: # (Github is primarily used for reporting bugs. If you have a feature request, use "Help > Request a Feature" so that the feature request can be vetted by the larger Habitica community)
|
[//]: # (Bugs in the mobile apps can also be reported there.)
|
||||||
|
|
||||||
[//]: # (To report a bug in one of the mobile apps, please report it in the correct repository. Android: https://github.com/HabitRPG/habitrpg-android, iOS: https://github.com/HabitRPG/habitrpg-ios)
|
[//]: # (If you have a feature request, use "Help > Request a Feature", not GitHub or the Report a Bug guild.)
|
||||||
|
|
||||||
[//]: # (For more guidelines see https://github.com/HabitRPG/habitrpg/issues/2760)
|
[//]: # (For more guidelines see https://github.com/HabitRPG/habitica/issues/2760)
|
||||||
|
|
||||||
[//]: # (Fill out relevant information - UUID is found in Settings -> API)
|
[//]: # (Fill out relevant information - UUID is found in Settings -> API)
|
||||||
General Info
|
### General Info
|
||||||
* UUID:
|
* UUID:
|
||||||
* Browser:
|
* Browser:
|
||||||
* OS:
|
* OS:
|
||||||
|
|
||||||
### Description
|
### Description
|
||||||
[//]: # (Describe bug in detail here. Include pictures if helpful.)
|
[//]: # (Describe bug in detail here. Include screenshots if helpful.)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#### Console Errors
|
#### Console Errors
|
||||||
[//]: # (Include any JavaScript console errors here.)
|
[//]: # (Include any JavaScript console errors here.)
|
||||||
|
|||||||
19
.travis.yml
19
.travis.yml
@@ -12,22 +12,25 @@ before_install:
|
|||||||
- $CXX --version
|
- $CXX --version
|
||||||
- npm install -g npm@4
|
- npm install -g npm@4
|
||||||
- if [ $REQUIRES_SERVER ]; then sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 7F0CEB10; echo 'deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen' | sudo tee /etc/apt/sources.list.d/mongodb.list; sudo apt-get update; sudo apt-get install mongodb-org-server; fi
|
- if [ $REQUIRES_SERVER ]; then sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 7F0CEB10; echo 'deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen' | sudo tee /etc/apt/sources.list.d/mongodb.list; sudo apt-get update; sudo apt-get install mongodb-org-server; fi
|
||||||
|
install:
|
||||||
|
- npm install &> npm.install.log || (cat npm.install.log; false)
|
||||||
before_script:
|
before_script:
|
||||||
- npm run test:build
|
- npm run test:build
|
||||||
- cp config.json.example config.json
|
- cp config.json.example config.json
|
||||||
- if [ $REQUIRES_SERVER ]; then until nc -z localhost 27017; do echo Waiting for MongoDB; sleep 1; done; export DISPLAY=:99; fi
|
- if [ $REQUIRES_SERVER ]; then until nc -z localhost 27017; do echo Waiting for MongoDB; sleep 1; done; export DISPLAY=:99; fi
|
||||||
after_script:
|
script:
|
||||||
- ./node_modules/.bin/lcov-result-merger 'coverage/**/*.info' | ./node_modules/coveralls/bin/coveralls.js
|
- npm run $TEST
|
||||||
script: npm run $TEST
|
- if [ $COVERAGE ]; then ./node_modules/.bin/lcov-result-merger 'coverage/**/*.info' | ./node_modules/coveralls/bin/coveralls.js; fi
|
||||||
env:
|
env:
|
||||||
global:
|
global:
|
||||||
- CXX=g++-4.8
|
- CXX=g++-4.8
|
||||||
- DISABLE_REQUEST_LOGGING=true
|
- DISABLE_REQUEST_LOGGING=true
|
||||||
matrix:
|
matrix:
|
||||||
- TEST="lint"
|
- TEST="lint"
|
||||||
- TEST="test:api-v3" REQUIRES_SERVER=true
|
- TEST="test:api-v3" REQUIRES_SERVER=true COVERAGE=true
|
||||||
- TEST="test:sanity"
|
- TEST="test:sanity"
|
||||||
- TEST="test:content"
|
- TEST="test:content" COVERAGE=true
|
||||||
- TEST="test:common"
|
- TEST="test:common" COVERAGE=true
|
||||||
- TEST="test:karma"
|
- TEST="test:karma" COVERAGE=true
|
||||||
- TEST="client:unit"
|
- TEST="client:unit" COVERAGE=true
|
||||||
|
- TEST="apidoc"
|
||||||
|
|||||||
37
Dockerfile
37
Dockerfile
@@ -1,43 +1,16 @@
|
|||||||
FROM ubuntu:trusty
|
FROM node:boron
|
||||||
|
|
||||||
MAINTAINER Sabe Jones <sabe@habitica.com>
|
|
||||||
|
|
||||||
# Avoid ERROR: invoke-rc.d: policy-rc.d denied execution of start.
|
|
||||||
RUN echo -e '#!/bin/sh\nexit 0' > /usr/sbin/policy-rc.d
|
|
||||||
|
|
||||||
# Install prerequisites
|
|
||||||
RUN apt-get update
|
|
||||||
RUN apt-get install -y \
|
|
||||||
build-essential \
|
|
||||||
curl \
|
|
||||||
git \
|
|
||||||
libfontconfig1 \
|
|
||||||
libfreetype6 \
|
|
||||||
libkrb5-dev \
|
|
||||||
python
|
|
||||||
|
|
||||||
# Install NodeJS
|
|
||||||
RUN curl -sL https://deb.nodesource.com/setup_6.x | sudo -E bash -
|
|
||||||
RUN apt-get install -y nodejs
|
|
||||||
|
|
||||||
# Install npm@latest
|
|
||||||
RUN curl -sL https://www.npmjs.org/install.sh | sh
|
|
||||||
|
|
||||||
# Clean up package management
|
|
||||||
RUN apt-get clean
|
|
||||||
RUN rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
# Install global packages
|
# Install global packages
|
||||||
RUN npm install -g gulp grunt-cli bower mocha
|
RUN npm install -g gulp grunt-cli bower mocha
|
||||||
|
|
||||||
# Clone Habitica repo and install dependencies
|
# Clone Habitica repo and install dependencies
|
||||||
WORKDIR /habitrpg
|
RUN mkdir -p /usr/src/habitrpg
|
||||||
RUN git clone https://github.com/HabitRPG/habitica.git /habitrpg
|
WORKDIR /usr/src/habitrpg
|
||||||
|
RUN git clone https://github.com/HabitRPG/habitica.git /usr/src/habitrpg
|
||||||
RUN npm install
|
RUN npm install
|
||||||
RUN bower install --allow-root
|
RUN bower install --allow-root
|
||||||
|
|
||||||
# Create environment config file and build directory
|
# Create Build dir
|
||||||
RUN cp config.json.example config.json
|
|
||||||
RUN mkdir -p ./website/build
|
RUN mkdir -p ./website/build
|
||||||
|
|
||||||
# Start Habitica
|
# Start Habitica
|
||||||
|
|||||||
@@ -5,8 +5,7 @@ Habitica [ - "Coders (Web & Mobile)" section.
|
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, see [Setting up Habitica Locally](http://habitica.wikia.com/wiki/Setting_up_Habitica_Locally), which contains instructions for Windows, *nix / Mac OS, and Vagrant.
|
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).
|
||||||
|
|
||||||
Then read [Guidance for Blacksmiths](http://habitica.wikia.com/wiki/Guidance_for_Blacksmiths) for additional instructions and useful tips.
|
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
"bootstrap-tour": "0.10.1",
|
"bootstrap-tour": "0.10.1",
|
||||||
"css-social-buttons": "samcollins/css-social-buttons#v1.1.1 ",
|
"css-social-buttons": "samcollins/css-social-buttons#v1.1.1 ",
|
||||||
"github-buttons": "mdo/github-buttons#v3.0.0",
|
"github-buttons": "mdo/github-buttons#v3.0.0",
|
||||||
"hello": "1.13.4",
|
"hello": "1.14.1",
|
||||||
"jquery": "2.1.0",
|
"jquery": "2.1.0",
|
||||||
"jquery-colorbox": "1.4.36",
|
"jquery-colorbox": "1.4.36",
|
||||||
"jquery-ui": "1.10.3",
|
"jquery-ui": "1.10.3",
|
||||||
|
|||||||
@@ -73,9 +73,14 @@
|
|||||||
"LOGGLY_ACCOUNT": "account",
|
"LOGGLY_ACCOUNT": "account",
|
||||||
"PUSH_CONFIGS": {
|
"PUSH_CONFIGS": {
|
||||||
"GCM_SERVER_API_KEY": "",
|
"GCM_SERVER_API_KEY": "",
|
||||||
"APN_ENABLED": "true",
|
"APN_ENABLED": "false",
|
||||||
"FCM_SERVER_API_KEY": ""
|
"FCM_SERVER_API_KEY": ""
|
||||||
},
|
},
|
||||||
|
"SITE_HTTP_AUTH": {
|
||||||
|
"ENABLED": "false",
|
||||||
|
"USERNAME": "admin",
|
||||||
|
"PASSWORD": "password"
|
||||||
|
},
|
||||||
"PUSHER": {
|
"PUSHER": {
|
||||||
"ENABLED": "false",
|
"ENABLED": "false",
|
||||||
"APP_ID": "appId",
|
"APP_ID": "appId",
|
||||||
@@ -86,5 +91,15 @@
|
|||||||
"FLAGGING_URL": "https://hooks.slack.com/services/id/id/id",
|
"FLAGGING_URL": "https://hooks.slack.com/services/id/id/id",
|
||||||
"FLAGGING_FOOTER_LINK": "https://habitrpg.github.io/flag-o-rama/",
|
"FLAGGING_FOOTER_LINK": "https://habitrpg.github.io/flag-o-rama/",
|
||||||
"SUBSCRIPTIONS_URL": "https://hooks.slack.com/services/id/id/id"
|
"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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ var authorUuid = 'd904bd62-da08-416b-a816-ba797c9ee265'; //... own data is done
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* database_reports/count_users_who_own_specified_gear.js
|
* database_reports/count_users_who_own_specified_gear.js
|
||||||
* https://github.com/HabitRPG/habitrpg/pull/3884
|
* https://github.com/HabitRPG/habitica/pull/3884
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var thingsOfInterest = {
|
var thingsOfInterest = {
|
||||||
|
|||||||
36
gulp/gulp-bootstrap.js
Normal file
36
gulp/gulp-bootstrap.js
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import gulp from 'gulp';
|
||||||
|
import fs from 'fs';
|
||||||
|
|
||||||
|
// Copy Bootstrap 4 config variables from /website /node_modules so we can check
|
||||||
|
// them into Git
|
||||||
|
|
||||||
|
const BOOSTRAP_NEW_CONFIG_PATH = 'website/client/assets/scss/bootstrap_config.scss';
|
||||||
|
const BOOTSTRAP_ORIGINAL_CONFIG_PATH = 'node_modules/bootstrap/scss/_custom.scss';
|
||||||
|
|
||||||
|
// https://stackoverflow.com/a/14387791/969528
|
||||||
|
function copyFile(source, target, cb) {
|
||||||
|
let cbCalled = false;
|
||||||
|
|
||||||
|
function done(err) {
|
||||||
|
if (!cbCalled) {
|
||||||
|
cb(err);
|
||||||
|
cbCalled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let rd = fs.createReadStream(source);
|
||||||
|
rd.on('error', done);
|
||||||
|
let wr = fs.createWriteStream(target);
|
||||||
|
wr.on('error', done);
|
||||||
|
wr.on('close', () => done());
|
||||||
|
rd.pipe(wr);
|
||||||
|
}
|
||||||
|
|
||||||
|
gulp.task('bootstrap', (done) => {
|
||||||
|
// use new config
|
||||||
|
copyFile(
|
||||||
|
BOOSTRAP_NEW_CONFIG_PATH,
|
||||||
|
BOOTSTRAP_ORIGINAL_CONFIG_PATH,
|
||||||
|
done,
|
||||||
|
);
|
||||||
|
});
|
||||||
@@ -25,7 +25,7 @@ gulp.task('build:common', () => {
|
|||||||
|
|
||||||
gulp.task('build:server', ['build:src', 'build:common']);
|
gulp.task('build:server', ['build:src', 'build:common']);
|
||||||
|
|
||||||
gulp.task('build:dev', ['browserify', 'prepare:staticNewStuff', 'semantic-ui'], (done) => {
|
gulp.task('build:dev', ['browserify', 'prepare:staticNewStuff'], (done) => {
|
||||||
gulp.start('grunt-build:dev', done);
|
gulp.start('grunt-build:dev', done);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@ gulp.task('build:dev:watch', ['build:dev'], () => {
|
|||||||
gulp.watch(['website/client-old/**/*.styl', 'website/common/script/*']);
|
gulp.watch(['website/client-old/**/*.styl', 'website/common/script/*']);
|
||||||
});
|
});
|
||||||
|
|
||||||
gulp.task('build:prod', ['browserify', 'build:server', 'prepare:staticNewStuff', 'semantic-ui'], (done) => {
|
gulp.task('build:prod', ['browserify', 'build:server', 'prepare:staticNewStuff'], (done) => {
|
||||||
runSequence(
|
runSequence(
|
||||||
'grunt-build:prod',
|
'grunt-build:prod',
|
||||||
'apidoc',
|
'apidoc',
|
||||||
|
|||||||
@@ -1,43 +0,0 @@
|
|||||||
import gulp from 'gulp';
|
|
||||||
import fs from 'fs';
|
|
||||||
|
|
||||||
// Make semantic-ui-less work with a theme in a different folder
|
|
||||||
// Code taken from https://www.artembutusov.com/webpack-semantic-ui/
|
|
||||||
|
|
||||||
// Relative to node_modules/semantic-ui-less
|
|
||||||
const SEMANTIC_THEME_PATH = '../../website/client/assets/less/semantic-ui/theme.config';
|
|
||||||
|
|
||||||
// fix well known bug with default distribution
|
|
||||||
function fixFontPath (filename) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
fs.readFile(filename, 'utf8', (err, content) => {
|
|
||||||
if (err) return reject(err);
|
|
||||||
|
|
||||||
let newContent = content.replace(
|
|
||||||
'@fontPath : \'../../themes/',
|
|
||||||
'@fontPath : \'../../../themes/'
|
|
||||||
);
|
|
||||||
|
|
||||||
fs.writeFile(filename, newContent, 'utf8', (err1) => {
|
|
||||||
if (err) return reject(err1);
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
gulp.task('semantic-ui', (done) => {
|
|
||||||
// relocate default config
|
|
||||||
fs.writeFile(
|
|
||||||
'node_modules/semantic-ui-less/theme.config',
|
|
||||||
`@import '${SEMANTIC_THEME_PATH}';\n`,
|
|
||||||
'utf8',
|
|
||||||
(err) => {
|
|
||||||
if (err) return done(err);
|
|
||||||
|
|
||||||
fixFontPath('node_modules/semantic-ui-less/themes/default/globals/site.variables')
|
|
||||||
.then(() => done())
|
|
||||||
.catch(done);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
@@ -49,7 +49,7 @@ gulp.task('sprites:checkCompiledDimensions', ['sprites:main', 'sprites:largeSpri
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (numberOfSheetsThatAreTooBig > 0) {
|
if (numberOfSheetsThatAreTooBig > 0) {
|
||||||
console.error(`${numberOfSheetsThatAreTooBig} sheets might too big for mobile Safari to be able to handle them, but there is a margin of error in these calculations so it is probably okay. Mention this to an admin so they can test a staging site on mobile Safari after your PR is merged.`); // https://github.com/HabitRPG/habitrpg/pull/6683#issuecomment-185462180
|
console.error(`${numberOfSheetsThatAreTooBig} sheets might too big for mobile Safari to be able to handle them, but there is a margin of error in these calculations so it is probably okay. Mention this to an admin so they can test a staging site on mobile Safari after your PR is merged.`); // https://github.com/HabitRPG/habitica/pull/6683#issuecomment-185462180
|
||||||
} else {
|
} else {
|
||||||
console.log('All images are within the correct dimensions');
|
console.log('All images are within the correct dimensions');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -280,7 +280,7 @@ gulp.task('test:e2e:safe', ['test:prepare', 'test:prepare:server'], (cb) => {
|
|||||||
|
|
||||||
gulp.task('test:api-v3:unit', (done) => {
|
gulp.task('test:api-v3:unit', (done) => {
|
||||||
let runner = exec(
|
let runner = exec(
|
||||||
testBin('mocha test/api/v3/unit --recursive --require ./test/helpers/start-server'),
|
testBin('node_modules/.bin/istanbul cover --dir coverage/api-v3-unit --report lcovonly node_modules/mocha/bin/_mocha -- test/api/v3/unit --recursive --require ./test/helpers/start-server'),
|
||||||
(err, stdout, stderr) => {
|
(err, stdout, stderr) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
@@ -298,7 +298,7 @@ gulp.task('test:api-v3:unit:watch', () => {
|
|||||||
|
|
||||||
gulp.task('test:api-v3:integration', (done) => {
|
gulp.task('test:api-v3:integration', (done) => {
|
||||||
let runner = exec(
|
let runner = exec(
|
||||||
testBin('mocha test/api/v3/integration --recursive --require ./test/helpers/start-server'),
|
testBin('node_modules/.bin/istanbul cover --dir coverage/api-v3-integration --report lcovonly node_modules/mocha/bin/_mocha -- test/api/v3/integration --recursive --require ./test/helpers/start-server'),
|
||||||
{maxBuffer: 500 * 1024},
|
{maxBuffer: 500 * 1024},
|
||||||
(err, stdout, stderr) => {
|
(err, stdout, stderr) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ gulp.task('transifex:malformedStrings', () => {
|
|||||||
let stringsWithIncorrectNumberOfInterpolations = [];
|
let stringsWithIncorrectNumberOfInterpolations = [];
|
||||||
|
|
||||||
let count = 0;
|
let count = 0;
|
||||||
_(ALL_LANGUAGES).each(function (lang) {
|
_.each(ALL_LANGUAGES, function (lang) {
|
||||||
|
|
||||||
_.each(stringsToLookFor, function (strings, file) {
|
_.each(stringsToLookFor, function (strings, file) {
|
||||||
let translationFile = fs.readFileSync(LOCALES + lang + '/' + file);
|
let translationFile = fs.readFileSync(LOCALES + lang + '/' + file);
|
||||||
@@ -89,7 +89,7 @@ gulp.task('transifex:malformedStrings', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}).value();
|
});
|
||||||
|
|
||||||
if (!_.isEmpty(stringsWithMalformedInterpolations)) {
|
if (!_.isEmpty(stringsWithMalformedInterpolations)) {
|
||||||
let message = 'The following strings have malformed or missing interpolations';
|
let message = 'The following strings have malformed or missing interpolations';
|
||||||
@@ -114,7 +114,7 @@ function getArrayOfLanguages () {
|
|||||||
function eachTranslationFile (languages, cb) {
|
function eachTranslationFile (languages, cb) {
|
||||||
let jsonFiles = stripOutNonJsonFiles(fs.readdirSync(ENGLISH_LOCALE));
|
let jsonFiles = stripOutNonJsonFiles(fs.readdirSync(ENGLISH_LOCALE));
|
||||||
|
|
||||||
_(languages).each((lang) => {
|
_.each(languages, (lang) => {
|
||||||
_.each(jsonFiles, (filename) => {
|
_.each(jsonFiles, (filename) => {
|
||||||
try {
|
try {
|
||||||
var translationFile = fs.readFileSync(LOCALES + lang + '/' + filename);
|
var translationFile = fs.readFileSync(LOCALES + lang + '/' + filename);
|
||||||
@@ -128,7 +128,7 @@ function eachTranslationFile (languages, cb) {
|
|||||||
|
|
||||||
cb(null, lang, filename, parsedEnglishFile, parsedTranslationFile);
|
cb(null, lang, filename, parsedEnglishFile, parsedTranslationFile);
|
||||||
});
|
});
|
||||||
}).value();
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function eachTranslationString (languages, cb) {
|
function eachTranslationString (languages, cb) {
|
||||||
@@ -153,7 +153,7 @@ function formatMessageForPosting (msg, items) {
|
|||||||
function getStringsWith (json, interpolationRegex) {
|
function getStringsWith (json, interpolationRegex) {
|
||||||
var strings = {};
|
var strings = {};
|
||||||
|
|
||||||
_(json).each(function (file_name) {
|
_.each(json, function (file_name) {
|
||||||
var raw_file = fs.readFileSync(ENGLISH_LOCALE + file_name);
|
var raw_file = fs.readFileSync(ENGLISH_LOCALE + file_name);
|
||||||
var parsed_json = JSON.parse(raw_file);
|
var parsed_json = JSON.parse(raw_file);
|
||||||
|
|
||||||
@@ -162,7 +162,7 @@ function getStringsWith (json, interpolationRegex) {
|
|||||||
var match = value.match(interpolationRegex);
|
var match = value.match(interpolationRegex);
|
||||||
if (match) strings[file_name][key] = match;
|
if (match) strings[file_name][key] = match;
|
||||||
});
|
});
|
||||||
}).value();
|
});
|
||||||
|
|
||||||
return strings;
|
return strings;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,6 @@
|
|||||||
require('babel-register');
|
require('babel-register');
|
||||||
|
|
||||||
if (process.env.NODE_ENV === 'production') {
|
if (process.env.NODE_ENV === 'production') {
|
||||||
require('./gulp/gulp-semanticui');
|
|
||||||
require('./gulp/gulp-apidoc');
|
require('./gulp/gulp-apidoc');
|
||||||
require('./gulp/gulp-newstuff');
|
require('./gulp/gulp-newstuff');
|
||||||
require('./gulp/gulp-build');
|
require('./gulp/gulp-build');
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
// %mongo server:27017/dbname underscore.js my_commands.js
|
// %mongo server:27017/dbname underscore.js my_commands.js
|
||||||
// %mongo server:27017/dbname underscore.js --shell
|
// %mongo server:27017/dbname underscore.js --shell
|
||||||
|
|
||||||
|
// IMPORTANT NOTE: this migration was written when we were using version 3 of lodash.
|
||||||
|
// We've now upgraded to lodash v4 but the code used in this migration has not been
|
||||||
|
// adapted to work with it. Before this migration is used again any lodash method should
|
||||||
|
// be checked for compatibility against the v4 changelog and changed if necessary.
|
||||||
|
// https://github.com/lodash/lodash/wiki/Changelog#v400
|
||||||
|
|
||||||
var habits = 0,
|
var habits = 0,
|
||||||
dailies = 0,
|
dailies = 0,
|
||||||
todos = 0,
|
todos = 0,
|
||||||
|
|||||||
@@ -3,6 +3,12 @@
|
|||||||
*/
|
*/
|
||||||
// mongo habitrpg ./node_modules/underscore/underscore.js ./migrations/20130326_migrate_pets.js
|
// mongo habitrpg ./node_modules/underscore/underscore.js ./migrations/20130326_migrate_pets.js
|
||||||
|
|
||||||
|
// IMPORTANT NOTE: this migration was written when we were using version 3 of lodash.
|
||||||
|
// We've now upgraded to lodash v4 but the code used in this migration has not been
|
||||||
|
// adapted to work with it. Before this migration is used again any lodash method should
|
||||||
|
// be checked for compatibility against the v4 changelog and changed if necessary.
|
||||||
|
// https://github.com/lodash/lodash/wiki/Changelog#v400
|
||||||
|
|
||||||
var mapping = {
|
var mapping = {
|
||||||
bearcub: {name:'BearCub', modifier: 'Base'},
|
bearcub: {name:'BearCub', modifier: 'Base'},
|
||||||
cactus: {name:'Cactus', modifier:'Base'},
|
cactus: {name:'Cactus', modifier:'Base'},
|
||||||
|
|||||||
@@ -4,6 +4,12 @@
|
|||||||
|
|
||||||
// mongo habitrpg ./node_modules/underscore/underscore.js migrations/20130327_apply_tokens.js
|
// mongo habitrpg ./node_modules/underscore/underscore.js migrations/20130327_apply_tokens.js
|
||||||
|
|
||||||
|
// IMPORTANT NOTE: this migration was written when we were using version 3 of lodash.
|
||||||
|
// We've now upgraded to lodash v4 but the code used in this migration has not been
|
||||||
|
// adapted to work with it. Before this migration is used again any lodash method should
|
||||||
|
// be checked for compatibility against the v4 changelog and changed if necessary.
|
||||||
|
// https://github.com/lodash/lodash/wiki/Changelog#v400
|
||||||
|
|
||||||
var mapping = [
|
var mapping = [
|
||||||
{
|
{
|
||||||
tier: 1,
|
tier: 1,
|
||||||
|
|||||||
@@ -6,6 +6,11 @@
|
|||||||
* mongo habitrpg ./node_modules/underscore/underscore.js ./migrations/20130508_fix_duff_party_subscriptions.js
|
* mongo habitrpg ./node_modules/underscore/underscore.js ./migrations/20130508_fix_duff_party_subscriptions.js
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// IMPORTANT NOTE: this migration was written when we were using version 3 of lodash.
|
||||||
|
// We've now upgraded to lodash v4 but the code used in this migration has not been
|
||||||
|
// adapted to work with it. Before this migration is used again any lodash method should
|
||||||
|
// be checked for compatibility against the v4 changelog and changed if necessary.
|
||||||
|
// https://github.com/lodash/lodash/wiki/Changelog#v400
|
||||||
|
|
||||||
// since our primary subscription will first hit parties now, we *definitely* need an index there
|
// since our primary subscription will first hit parties now, we *definitely* need an index there
|
||||||
db.parties.ensureIndex( { 'members': 1}, {background: true} );
|
db.parties.ensureIndex( { 'members': 1}, {background: true} );
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
//mongo habitrpg ./node_modules/lodash/lodash.js migrations/20130602_survey_rewards.js
|
//mongo habitrpg ./node_modules/lodash/lodash.js migrations/20130602_survey_rewards.js
|
||||||
|
|
||||||
|
// IMPORTANT NOTE: this migration was written when we were using version 3 of lodash.
|
||||||
|
// We've now upgraded to lodash v4 but the code used in this migration has not been
|
||||||
|
// adapted to work with it. Before this migration is used again any lodash method should
|
||||||
|
// be checked for compatibility against the v4 changelog and changed if necessary.
|
||||||
|
// https://github.com/lodash/lodash/wiki/Changelog#v400
|
||||||
|
|
||||||
var members = []
|
var members = []
|
||||||
members = _.uniq(members);
|
members = _.uniq(members);
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,12 @@
|
|||||||
// Racer was notorious for adding duplicates, randomly deleting documents, etc. Once we pull the plug on old.habit,
|
// Racer was notorious for adding duplicates, randomly deleting documents, etc. Once we pull the plug on old.habit,
|
||||||
// run this migration to cleanup all the corruption
|
// run this migration to cleanup all the corruption
|
||||||
|
|
||||||
|
// IMPORTANT NOTE: this migration was written when we were using version 3 of lodash.
|
||||||
|
// We've now upgraded to lodash v4 but the code used in this migration has not been
|
||||||
|
// adapted to work with it. Before this migration is used again any lodash method should
|
||||||
|
// be checked for compatibility against the v4 changelog and changed if necessary.
|
||||||
|
// https://github.com/lodash/lodash/wiki/Changelog#v400
|
||||||
|
|
||||||
db.users.find().forEach(function(user){
|
db.users.find().forEach(function(user){
|
||||||
|
|
||||||
// remove corrupt tasks, which will either be null-value or no id
|
// remove corrupt tasks, which will either be null-value or no id
|
||||||
|
|||||||
@@ -5,6 +5,12 @@
|
|||||||
// @see http://stackoverflow.com/questions/14867697/mongoose-full-collection-scan
|
// @see http://stackoverflow.com/questions/14867697/mongoose-full-collection-scan
|
||||||
//Also, what do we think of a Mongoose Migration module? something like https://github.com/madhums/mongoose-migrate
|
//Also, what do we think of a Mongoose Migration module? something like https://github.com/madhums/mongoose-migrate
|
||||||
|
|
||||||
|
// IMPORTANT NOTE: this migration was written when we were using version 3 of lodash.
|
||||||
|
// We've now upgraded to lodash v4 but the code used in this migration has not been
|
||||||
|
// adapted to work with it. Before this migration is used again any lodash method should
|
||||||
|
// be checked for compatibility against the v4 changelog and changed if necessary.
|
||||||
|
// https://github.com/lodash/lodash/wiki/Changelog#v400
|
||||||
|
|
||||||
db.users.find().forEach(function(user){
|
db.users.find().forEach(function(user){
|
||||||
|
|
||||||
// Add invites to groups
|
// Add invites to groups
|
||||||
|
|||||||
@@ -8,6 +8,12 @@
|
|||||||
var mongo = require('mongoskin');
|
var mongo = require('mongoskin');
|
||||||
var _ = require('lodash');
|
var _ = require('lodash');
|
||||||
|
|
||||||
|
// IMPORTANT NOTE: this migration was written when we were using version 3 of lodash.
|
||||||
|
// We've now upgraded to lodash v4 but the code used in this migration has not been
|
||||||
|
// adapted to work with it. Before this migration is used again any lodash method should
|
||||||
|
// be checked for compatibility against the v4 changelog and changed if necessary.
|
||||||
|
// https://github.com/lodash/lodash/wiki/Changelog#v400
|
||||||
|
|
||||||
var backupUsers = mongo.db('localhost:27017/habitrpg_old?auto_reconnect').collection('users');
|
var backupUsers = mongo.db('localhost:27017/habitrpg_old?auto_reconnect').collection('users');
|
||||||
var liveUsers = mongo.db('localhost:27017/habitrpg_new?auto_reconnect').collection('users');
|
var liveUsers = mongo.db('localhost:27017/habitrpg_new?auto_reconnect').collection('users');
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
// node .migrations/20131127_restore_dayStart.js
|
// node .migrations/20131127_restore_dayStart.js
|
||||||
|
|
||||||
|
// IMPORTANT NOTE: this migration was written when we were using version 3 of lodash.
|
||||||
|
// We've now upgraded to lodash v4 but the code used in this migration has not been
|
||||||
|
// adapted to work with it. Before this migration is used again any lodash method should
|
||||||
|
// be checked for compatibility against the v4 changelog and changed if necessary.
|
||||||
|
// https://github.com/lodash/lodash/wiki/Changelog#v400
|
||||||
|
|
||||||
var mongo = require('mongoskin');
|
var mongo = require('mongoskin');
|
||||||
var _ = require('lodash');
|
var _ = require('lodash');
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,12 @@ mongo = require('mongoskin')
|
|||||||
_ = require('lodash')
|
_ = require('lodash')
|
||||||
async = require('async')
|
async = require('async')
|
||||||
|
|
||||||
|
# IMPORTANT NOTE: this migration was written when we were using version 3 of lodash.
|
||||||
|
# We've now upgraded to lodash v4 but the code used in this migration has not been
|
||||||
|
# adapted to work with it. Before this migration is used again any lodash method should
|
||||||
|
# be checked for compatibility against the v4 changelog and changed if necessary.
|
||||||
|
# https://github.com/lodash/lodash/wiki/Changelog#v400
|
||||||
|
|
||||||
db = mongo.db('localhost:27017/habitrpg?auto_reconnect')
|
db = mongo.db('localhost:27017/habitrpg?auto_reconnect')
|
||||||
|
|
||||||
###
|
###
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
// node .migrations/20131221_restore_NaN_history.js
|
// node .migrations/20131221_restore_NaN_history.js
|
||||||
|
|
||||||
|
// IMPORTANT NOTE: this migration was written when we were using version 3 of lodash.
|
||||||
|
// We've now upgraded to lodash v4 but the code used in this migration has not been
|
||||||
|
// adapted to work with it. Before this migration is used again any lodash method should
|
||||||
|
// be checked for compatibility against the v4 changelog and changed if necessary.
|
||||||
|
// https://github.com/lodash/lodash/wiki/Changelog#v400
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* After the classes migration, users lost some history entries
|
* After the classes migration, users lost some history entries
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
// node .migrations/20131225_restore_streaks.js
|
// node .migrations/20131225_restore_streaks.js
|
||||||
|
|
||||||
|
// IMPORTANT NOTE: this migration was written when we were using version 3 of lodash.
|
||||||
|
// We've now upgraded to lodash v4 but the code used in this migration has not been
|
||||||
|
// adapted to work with it. Before this migration is used again any lodash method should
|
||||||
|
// be checked for compatibility against the v4 changelog and changed if necessary.
|
||||||
|
// https://github.com/lodash/lodash/wiki/Changelog#v400
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* After the classes migration, users lost some history entries
|
* After the classes migration, users lost some history entries
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -5,6 +5,12 @@ var migrationName = '20140823_remove_undefined_and_false_notifications';
|
|||||||
var authorName = 'Alys'; // in case script author needs to know when their ...
|
var authorName = 'Alys'; // in case script author needs to know when their ...
|
||||||
var authorUuid = 'd904bd62-da08-416b-a816-ba797c9ee265'; //... own data is done
|
var authorUuid = 'd904bd62-da08-416b-a816-ba797c9ee265'; //... own data is done
|
||||||
|
|
||||||
|
// IMPORTANT NOTE: this migration was written when we were using version 3 of lodash.
|
||||||
|
// We've now upgraded to lodash v4 but the code used in this migration has not been
|
||||||
|
// adapted to work with it. Before this migration is used again any lodash method should
|
||||||
|
// be checked for compatibility against the v4 changelog and changed if necessary.
|
||||||
|
// https://github.com/lodash/lodash/wiki/Changelog#v400
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* https://github.com/HabitRPG/habitrpg/pull/3907
|
* https://github.com/HabitRPG/habitrpg/pull/3907
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -4,6 +4,12 @@ var migrationName = '20140829_change_headAccessory_to_eyewear';
|
|||||||
var authorName = 'Alys'; // in case script author needs to know when their ...
|
var authorName = 'Alys'; // in case script author needs to know when their ...
|
||||||
var authorUuid = 'd904bd62-da08-416b-a816-ba797c9ee265'; //... own data is done
|
var authorUuid = 'd904bd62-da08-416b-a816-ba797c9ee265'; //... own data is done
|
||||||
|
|
||||||
|
// IMPORTANT NOTE: this migration was written when we were using version 3 of lodash.
|
||||||
|
// We've now upgraded to lodash v4 but the code used in this migration has not been
|
||||||
|
// adapted to work with it. Before this migration is used again any lodash method should
|
||||||
|
// be checked for compatibility against the v4 changelog and changed if necessary.
|
||||||
|
// https://github.com/lodash/lodash/wiki/Changelog#v400
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* https://github.com/HabitRPG/habitrpg/issues/3645
|
* https://github.com/HabitRPG/habitrpg/issues/3645
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -4,6 +4,11 @@
|
|||||||
//
|
//
|
||||||
// node 20140831_increase_gems_for_previous_contributions.js > 20140831_increase_gems_for_previous_contributions_output.txt
|
// node 20140831_increase_gems_for_previous_contributions.js > 20140831_increase_gems_for_previous_contributions_output.txt
|
||||||
|
|
||||||
|
// IMPORTANT NOTE: this migration was written when we were using version 3 of lodash.
|
||||||
|
// We've now upgraded to lodash v4 but the code used in this migration has not been
|
||||||
|
// adapted to work with it. Before this migration is used again any lodash method should
|
||||||
|
// be checked for compatibility against the v4 changelog and changed if necessary.
|
||||||
|
// https://github.com/lodash/lodash/wiki/Changelog#v400
|
||||||
|
|
||||||
var migrationName = '20140831_increase_gems_for_previous_contributions';
|
var migrationName = '20140831_increase_gems_for_previous_contributions';
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,12 @@ var authorUuid = 'd904bd62-da08-416b-a816-ba797c9ee265'; //... own data is done
|
|||||||
* Convert Tier 7 contributors with admin flag to Tier 8 (moderators).
|
* Convert Tier 7 contributors with admin flag to Tier 8 (moderators).
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// IMPORTANT NOTE: this migration was written when we were using version 3 of lodash.
|
||||||
|
// We've now upgraded to lodash v4 but the code used in this migration has not been
|
||||||
|
// adapted to work with it. Before this migration is used again any lodash method should
|
||||||
|
// be checked for compatibility against the v4 changelog and changed if necessary.
|
||||||
|
// https://github.com/lodash/lodash/wiki/Changelog#v400
|
||||||
|
|
||||||
var mongo = require('mongoskin');
|
var mongo = require('mongoskin');
|
||||||
var _ = require('lodash');
|
var _ = require('lodash');
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
// IMPORTANT NOTE: this migration was written when we were using version 3 of lodash.
|
||||||
|
// We've now upgraded to lodash v4 but the code used in this migration has not been
|
||||||
|
// adapted to work with it. Before this migration is used again any lodash method should
|
||||||
|
// be checked for compatibility against the v4 changelog and changed if necessary.
|
||||||
|
// https://github.com/lodash/lodash/wiki/Changelog#v400
|
||||||
|
|
||||||
// require moment, lodash
|
// require moment, lodash
|
||||||
db.users.find(
|
db.users.find(
|
||||||
{'purchased.plan.customerId':{$ne:null}},
|
{'purchased.plan.customerId':{$ne:null}},
|
||||||
|
|||||||
@@ -9,6 +9,12 @@ var authorUuid = 'd904bd62-da08-416b-a816-ba797c9ee265'; //... own data is done
|
|||||||
|
|
||||||
var dbserver = 'localhost:27017' // CHANGE THIS FOR PRODUCTION DATABASE
|
var dbserver = 'localhost:27017' // CHANGE THIS FOR PRODUCTION DATABASE
|
||||||
|
|
||||||
|
// IMPORTANT NOTE: this migration was written when we were using version 3 of lodash.
|
||||||
|
// We've now upgraded to lodash v4 but the code used in this migration has not been
|
||||||
|
// adapted to work with it. Before this migration is used again any lodash method should
|
||||||
|
// be checked for compatibility against the v4 changelog and changed if necessary.
|
||||||
|
// https://github.com/lodash/lodash/wiki/Changelog#v400
|
||||||
|
|
||||||
var mongo = require('mongoskin');
|
var mongo = require('mongoskin');
|
||||||
var _ = require('lodash');
|
var _ = require('lodash');
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,12 @@ var authorUuid = 'd904bd62-da08-416b-a816-ba797c9ee265'; //... own data is done
|
|||||||
|
|
||||||
var dbserver = 'localhost:27017' // CHANGE THIS FOR PRODUCTION DATABASE
|
var dbserver = 'localhost:27017' // CHANGE THIS FOR PRODUCTION DATABASE
|
||||||
|
|
||||||
|
// IMPORTANT NOTE: this migration was written when we were using version 3 of lodash.
|
||||||
|
// We've now upgraded to lodash v4 but the code used in this migration has not been
|
||||||
|
// adapted to work with it. Before this migration is used again any lodash method should
|
||||||
|
// be checked for compatibility against the v4 changelog and changed if necessary.
|
||||||
|
// https://github.com/lodash/lodash/wiki/Changelog#v400
|
||||||
|
|
||||||
var mongo = require('mongoskin');
|
var mongo = require('mongoskin');
|
||||||
var _ = require('lodash');
|
var _ = require('lodash');
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,12 @@ var authorUuid = 'd904bd62-da08-416b-a816-ba797c9ee265'; //... own data is done
|
|||||||
|
|
||||||
var dbserver = 'localhost:27017' // CHANGE THIS FOR PRODUCTION DATABASE
|
var dbserver = 'localhost:27017' // CHANGE THIS FOR PRODUCTION DATABASE
|
||||||
|
|
||||||
|
// IMPORTANT NOTE: this migration was written when we were using version 3 of lodash.
|
||||||
|
// We've now upgraded to lodash v4 but the code used in this migration has not been
|
||||||
|
// adapted to work with it. Before this migration is used again any lodash method should
|
||||||
|
// be checked for compatibility against the v4 changelog and changed if necessary.
|
||||||
|
// https://github.com/lodash/lodash/wiki/Changelog#v400
|
||||||
|
|
||||||
var mongo = require('mongoskin');
|
var mongo = require('mongoskin');
|
||||||
var _ = require('lodash');
|
var _ = require('lodash');
|
||||||
var moment = require('moment');
|
var moment = require('moment');
|
||||||
|
|||||||
@@ -6,6 +6,12 @@ var authorUuid = 'd904bd62-da08-416b-a816-ba797c9ee265'; //... own data is done
|
|||||||
* force all active players to rest in the inn due to massive server fail
|
* force all active players to rest in the inn due to massive server fail
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// IMPORTANT NOTE: this migration was written when we were using version 3 of lodash.
|
||||||
|
// We've now upgraded to lodash v4 but the code used in this migration has not been
|
||||||
|
// adapted to work with it. Before this migration is used again any lodash method should
|
||||||
|
// be checked for compatibility against the v4 changelog and changed if necessary.
|
||||||
|
// https://github.com/lodash/lodash/wiki/Changelog#v400
|
||||||
|
|
||||||
var dbserver = 'localhost:27017' // CHANGE THIS FOR PRODUCTION DATABASE
|
var dbserver = 'localhost:27017' // CHANGE THIS FOR PRODUCTION DATABASE
|
||||||
|
|
||||||
var mongo = require('mongoskin');
|
var mongo = require('mongoskin');
|
||||||
|
|||||||
@@ -19,6 +19,12 @@ var authorUuid = 'd904bd62-da08-416b-a816-ba797c9ee265'; //... own data is done
|
|||||||
* means minimal new testing.
|
* means minimal new testing.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// IMPORTANT NOTE: this migration was written when we were using version 3 of lodash.
|
||||||
|
// We've now upgraded to lodash v4 but the code used in this migration has not been
|
||||||
|
// adapted to work with it. Before this migration is used again any lodash method should
|
||||||
|
// be checked for compatibility against the v4 changelog and changed if necessary.
|
||||||
|
// https://github.com/lodash/lodash/wiki/Changelog#v400
|
||||||
|
|
||||||
var dbserver = 'localhost:27017' // FOR TEST DATABASE
|
var dbserver = 'localhost:27017' // FOR TEST DATABASE
|
||||||
// var dbserver = 'username:password@ds031379-a0.mongolab.com:31379' // FOR PRODUCTION DATABASE
|
// var dbserver = 'username:password@ds031379-a0.mongolab.com:31379' // FOR PRODUCTION DATABASE
|
||||||
var dbname = 'habitrpg';
|
var dbname = 'habitrpg';
|
||||||
|
|||||||
@@ -7,6 +7,12 @@ var migrationName = '20160111_challenges_condense_same_day_history_entries.js';
|
|||||||
var dbserver = '';
|
var dbserver = '';
|
||||||
var dbname = '';
|
var dbname = '';
|
||||||
|
|
||||||
|
// IMPORTANT NOTE: this migration was written when we were using version 3 of lodash.
|
||||||
|
// We've now upgraded to lodash v4 but the code used in this migration has not been
|
||||||
|
// adapted to work with it. Before this migration is used again any lodash method should
|
||||||
|
// be checked for compatibility against the v4 changelog and changed if necessary.
|
||||||
|
// https://github.com/lodash/lodash/wiki/Changelog#v400
|
||||||
|
|
||||||
var mongo = require('mongoskin');
|
var mongo = require('mongoskin');
|
||||||
var _ = require('lodash');
|
var _ = require('lodash');
|
||||||
var moment = require('moment');
|
var moment = require('moment');
|
||||||
|
|||||||
@@ -11,6 +11,12 @@ var dbserver = 'localhost:27017'; // FOR TEST DATABASE
|
|||||||
// var dbserver = 'username:password@ds031379-a0.mongolab.com:31379'; // FOR PRODUCTION DATABASE
|
// var dbserver = 'username:password@ds031379-a0.mongolab.com:31379'; // FOR PRODUCTION DATABASE
|
||||||
var dbname = 'habitrpg';
|
var dbname = 'habitrpg';
|
||||||
|
|
||||||
|
// IMPORTANT NOTE: this migration was written when we were using version 3 of lodash.
|
||||||
|
// We've now upgraded to lodash v4 but the code used in this migration has not been
|
||||||
|
// adapted to work with it. Before this migration is used again any lodash method should
|
||||||
|
// be checked for compatibility against the v4 changelog and changed if necessary.
|
||||||
|
// https://github.com/lodash/lodash/wiki/Changelog#v400
|
||||||
|
|
||||||
var mongo = require('mongoskin');
|
var mongo = require('mongoskin');
|
||||||
var _ = require('lodash');
|
var _ = require('lodash');
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,12 @@ var dbname = 'habitrpg';
|
|||||||
var mongo = require('mongoskin');
|
var mongo = require('mongoskin');
|
||||||
var _ = require('lodash');
|
var _ = require('lodash');
|
||||||
|
|
||||||
|
// IMPORTANT NOTE: this migration was written when we were using version 3 of lodash.
|
||||||
|
// We've now upgraded to lodash v4 but the code used in this migration has not been
|
||||||
|
// adapted to work with it. Before this migration is used again any lodash method should
|
||||||
|
// be checked for compatibility against the v4 changelog and changed if necessary.
|
||||||
|
// https://github.com/lodash/lodash/wiki/Changelog#v400
|
||||||
|
|
||||||
var dbUsers = mongo.db(dbserver + '/' + dbname + '?auto_reconnect').collection('users');
|
var dbUsers = mongo.db(dbserver + '/' + dbname + '?auto_reconnect').collection('users');
|
||||||
|
|
||||||
// specify a query to limit the affected users (empty for all users):
|
// specify a query to limit the affected users (empty for all users):
|
||||||
|
|||||||
@@ -2,6 +2,12 @@ var uuid = require('uuid').v4;
|
|||||||
var mongo = require('mongodb').MongoClient;
|
var mongo = require('mongodb').MongoClient;
|
||||||
var _ = require('lodash');
|
var _ = require('lodash');
|
||||||
|
|
||||||
|
// IMPORTANT NOTE: this migration was written when we were using version 3 of lodash.
|
||||||
|
// We've now upgraded to lodash v4 but the code used in this migration has not been
|
||||||
|
// adapted to work with it. Before this migration is used again any lodash method should
|
||||||
|
// be checked for compatibility against the v4 changelog and changed if necessary.
|
||||||
|
// https://github.com/lodash/lodash/wiki/Changelog#v400
|
||||||
|
|
||||||
var taskIds = require('checklists-no-id.json').map(function (obj) {
|
var taskIds = require('checklists-no-id.json').map(function (obj) {
|
||||||
return obj._id;
|
return obj._id;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,110 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
/****************************************
|
||||||
|
* Author: @Alys
|
||||||
|
*
|
||||||
|
* Reason: Collection quests are being changed
|
||||||
|
* to require fewer items collected:
|
||||||
|
* https://github.com/HabitRPG/habitrpg/pull/7987
|
||||||
|
* This will cause existing quests to end sooner
|
||||||
|
* than the party is expecting.
|
||||||
|
* This script inserts an explanatory `system`
|
||||||
|
* message into the chat for affected parties.
|
||||||
|
***************************************/
|
||||||
|
|
||||||
|
global.Promise = require('bluebird');
|
||||||
|
const uuid = require('uuid');
|
||||||
|
const TaskQueue = require('cwait').TaskQueue;
|
||||||
|
const logger = require('./utils/logger');
|
||||||
|
const Timer = require('./utils/timer');
|
||||||
|
const connectToDb = require('./utils/connect').connectToDb;
|
||||||
|
const closeDb = require('./utils/connect').closeDb;
|
||||||
|
|
||||||
|
const message = '`This party\'s collection quest has been made easier! For details, refer to http://habitica.wikia.com/wiki/User_blog:LadyAlys/Collection_Quests_are_Now_Easier`';
|
||||||
|
|
||||||
|
const timer = new Timer();
|
||||||
|
|
||||||
|
// PROD: Enable prod db
|
||||||
|
// const DB_URI = 'mongodb://username:password@dsXXXXXX-a0.mlab.com:XXXXX,dsXXXXXX-a1.mlab.com:XXXXX/habitica?replicaSet=rs-dsXXXXXX';
|
||||||
|
const DB_URI = 'mongodb://localhost/habitrpg';
|
||||||
|
|
||||||
|
const COLLECTION_QUESTS = [
|
||||||
|
'vice2',
|
||||||
|
'egg',
|
||||||
|
'moonstone1',
|
||||||
|
'goldenknight1',
|
||||||
|
'dilatoryDistress1',
|
||||||
|
];
|
||||||
|
|
||||||
|
let Groups;
|
||||||
|
|
||||||
|
connectToDb(DB_URI).then((db) => {
|
||||||
|
Groups = db.collection('groups');
|
||||||
|
|
||||||
|
return Promise.resolve();
|
||||||
|
})
|
||||||
|
.then(findPartiesWithCollectionQuest)
|
||||||
|
// .then(displayGroups) // for testing only
|
||||||
|
.then(addMessageToGroups)
|
||||||
|
.then(() => {
|
||||||
|
timer.stop();
|
||||||
|
closeDb();
|
||||||
|
}).catch(reportError);
|
||||||
|
|
||||||
|
function reportError (err) {
|
||||||
|
logger.error('Uh oh, an error occurred');
|
||||||
|
closeDb();
|
||||||
|
timer.stop();
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
|
||||||
|
function findPartiesWithCollectionQuest () {
|
||||||
|
logger.info('Looking up groups on collection quests...');
|
||||||
|
|
||||||
|
return Groups.find({'quest.key': {$in: COLLECTION_QUESTS}}, ['name','quest']).toArray().then((groups) => {
|
||||||
|
logger.success('Found', groups.length, 'parties on collection quests');
|
||||||
|
|
||||||
|
return Promise.resolve(groups);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function displayGroups (groups) { // for testing only
|
||||||
|
logger.info('Displaying parties...');
|
||||||
|
console.log(groups);
|
||||||
|
return Promise.resolve(groups);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateGroupById (group) {
|
||||||
|
var newMessage = {
|
||||||
|
'id' : uuid.v4(),
|
||||||
|
'text' : message,
|
||||||
|
'timestamp': Date.now(),
|
||||||
|
'likes': {},
|
||||||
|
'flags': {},
|
||||||
|
'flagCount': 0,
|
||||||
|
'uuid': 'system'
|
||||||
|
};
|
||||||
|
return Groups.findOneAndUpdate({_id: group._id}, {$push:{"chat" :{$each: [newMessage], $position:0}}}, {returnOriginal: false});
|
||||||
|
// Does not set the newMessage flag for all party members because I don't think it's essential and
|
||||||
|
// I don't want to run the extra code (extra database load, extra opportunity for bugs).
|
||||||
|
}
|
||||||
|
|
||||||
|
function addMessageToGroups (groups) {
|
||||||
|
let queue = new TaskQueue(Promise, 300);
|
||||||
|
|
||||||
|
logger.info('About to update', groups.length, 'parties...');
|
||||||
|
|
||||||
|
return Promise.map(groups, queue.wrap(updateGroupById)).then((result) => {
|
||||||
|
let updates = result.filter(res => res.lastErrorObject && res.lastErrorObject.updatedExisting)
|
||||||
|
let failures = result.filter(res => !(res.lastErrorObject && res.lastErrorObject.updatedExisting));
|
||||||
|
|
||||||
|
logger.success(updates.length, 'parties have been notified');
|
||||||
|
|
||||||
|
if (failures.length > 0) {
|
||||||
|
logger.error(failures.length, 'parties could not be notified');
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.resolve();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
88
migrations/20170418_subscriber_jackalopes.js
Normal file
88
migrations/20170418_subscriber_jackalopes.js
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
var migrationName = '20170418_subscriber_jackalopes.js';
|
||||||
|
var authorName = 'Sabe'; // in case script author needs to know when their ...
|
||||||
|
var authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; //... own data is done
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Award Royal Purple Jackalope pet to all current subscribers
|
||||||
|
*/
|
||||||
|
|
||||||
|
var monk = require('monk');
|
||||||
|
var connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
|
||||||
|
var dbUsers = monk(connectionString).get('users', { castIds: false });
|
||||||
|
var now = new Date();
|
||||||
|
|
||||||
|
function processUsers(lastId) {
|
||||||
|
// specify a query to limit the affected users (empty for all users):
|
||||||
|
var query = {
|
||||||
|
'purchased.plan.customerId': {$type: 2},
|
||||||
|
$or: [
|
||||||
|
{'purchased.plan.dateTerminated': null},
|
||||||
|
{'purchased.plan.dateTerminated': {$exists: false}},
|
||||||
|
{'purchased.plan.dateTerminated': {$gt: now}},
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
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(function (err) {
|
||||||
|
console.log(err);
|
||||||
|
return exiting(1, 'ERROR! ' + err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var progressCount = 1000;
|
||||||
|
var count = 0;
|
||||||
|
|
||||||
|
function updateUsers (users) {
|
||||||
|
if (!users || users.length === 0) {
|
||||||
|
console.warn('All appropriate users found and modified.');
|
||||||
|
displayData();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var userPromises = users.map(updateUser);
|
||||||
|
var lastUser = users[users.length - 1];
|
||||||
|
|
||||||
|
return Promise.all(userPromises)
|
||||||
|
.then(function () {
|
||||||
|
processUsers(lastUser._id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateUser (user) {
|
||||||
|
count++;
|
||||||
|
|
||||||
|
var set = {'items.pets.Jackalope-RoyalPurple': 5};
|
||||||
|
|
||||||
|
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;
|
||||||
207
migrations/20170425_missing_incentives.js
Normal file
207
migrations/20170425_missing_incentives.js
Normal file
@@ -0,0 +1,207 @@
|
|||||||
|
var migrationName = '20170425_missing_incentives';
|
||||||
|
var authorName = 'Sabe'; // in case script author needs to know when their ...
|
||||||
|
var authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; //... own data is done
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Award missing Royal Purple Hatching Potion to users with 55+ check-ins
|
||||||
|
* Reduce users with impossible check-in counts to a reasonable number
|
||||||
|
*/
|
||||||
|
|
||||||
|
import monk from 'monk';
|
||||||
|
import common from '../website/common';
|
||||||
|
|
||||||
|
var connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
|
||||||
|
var dbUsers = monk(connectionString).get('users', { castIds: false });
|
||||||
|
|
||||||
|
function processUsers(lastId) {
|
||||||
|
// specify a query to limit the affected users (empty for all users):
|
||||||
|
var query = {
|
||||||
|
'loginIncentives': {$gt:99},
|
||||||
|
'migration': {$ne: migrationName},
|
||||||
|
};
|
||||||
|
|
||||||
|
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(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 language = user.preferences.language || 'en';
|
||||||
|
var set = {'migration': migrationName};
|
||||||
|
var inc = {
|
||||||
|
'items.eggs.BearCub': 0,
|
||||||
|
'items.eggs.Cactus': 0,
|
||||||
|
'items.eggs.Dragon': 0,
|
||||||
|
'items.eggs.FlyingPig': 0,
|
||||||
|
'items.eggs.Fox': 0,
|
||||||
|
'items.eggs.LionCub': 0,
|
||||||
|
'items.eggs.PandaCub': 0,
|
||||||
|
'items.eggs.TigerCub': 0,
|
||||||
|
'items.eggs.Wolf': 0,
|
||||||
|
'items.food.Chocolate': 0,
|
||||||
|
'items.food.CottonCandyBlue': 0,
|
||||||
|
'items.food.CottonCandyPink': 0,
|
||||||
|
'items.food.Fish': 0,
|
||||||
|
'items.food.Honey': 0,
|
||||||
|
'items.food.Meat': 0,
|
||||||
|
'items.food.Milk': 0,
|
||||||
|
'items.food.Potatoe': 0,
|
||||||
|
'items.food.RottenMeat': 0,
|
||||||
|
'items.food.Strawberry': 0,
|
||||||
|
'items.hatchingPotions.Base': 0,
|
||||||
|
'items.hatchingPotions.CottonCandyBlue': 0,
|
||||||
|
'items.hatchingPotions.CottonCandyPink': 0,
|
||||||
|
'items.hatchingPotions.Desert': 0,
|
||||||
|
'items.hatchingPotions.Golden': 0,
|
||||||
|
'items.hatchingPotions.Red': 0,
|
||||||
|
'items.hatchingPotions.RoyalPurple': 0,
|
||||||
|
'items.hatchingPotions.Shade': 0,
|
||||||
|
'items.hatchingPotions.Skeleton': 0,
|
||||||
|
'items.hatchingPotions.White': 0,
|
||||||
|
'items.hatchingPotions.Zombie': 0,
|
||||||
|
};
|
||||||
|
var nextReward;
|
||||||
|
|
||||||
|
if (user.loginIncentives >= 105) {
|
||||||
|
inc['items.hatchingPotions.RoyalPurple'] += 1;
|
||||||
|
nextReward = 110;
|
||||||
|
}
|
||||||
|
if (user.loginIncentives >= 110) {
|
||||||
|
inc['items.eggs.BearCub'] += 1;
|
||||||
|
inc['items.eggs.Cactus'] += 1;
|
||||||
|
inc['items.eggs.Dragon'] += 1;
|
||||||
|
inc['items.eggs.FlyingPig'] += 1;
|
||||||
|
inc['items.eggs.Fox'] += 1;
|
||||||
|
inc['items.eggs.LionCub'] += 1;
|
||||||
|
inc['items.eggs.PandaCub'] += 1;
|
||||||
|
inc['items.eggs.TigerCub'] += 1;
|
||||||
|
inc['items.eggs.Wolf'] += 1;
|
||||||
|
nextReward = 115;
|
||||||
|
}
|
||||||
|
if (user.loginIncentives >= 115) {
|
||||||
|
inc['items.hatchingPotions.RoyalPurple'] += 1;
|
||||||
|
nextReward = 120;
|
||||||
|
}
|
||||||
|
if (user.loginIncentives >= 120) {
|
||||||
|
inc['items.hatchingPotions.Base'] += 1;
|
||||||
|
inc['items.hatchingPotions.CottonCandyBlue'] += 1;
|
||||||
|
inc['items.hatchingPotions.CottonCandyPink'] += 1;
|
||||||
|
inc['items.hatchingPotions.Desert'] += 1;
|
||||||
|
inc['items.hatchingPotions.Golden'] += 1;
|
||||||
|
inc['items.hatchingPotions.Red'] += 1;
|
||||||
|
inc['items.hatchingPotions.Shade'] += 1;
|
||||||
|
inc['items.hatchingPotions.Skeleton'] += 1;
|
||||||
|
inc['items.hatchingPotions.White'] += 1;
|
||||||
|
inc['items.hatchingPotions.Zombie'] += 1;
|
||||||
|
nextReward = 125;
|
||||||
|
}
|
||||||
|
if (user.loginIncentives >= 125) {
|
||||||
|
inc['items.hatchingPotions.RoyalPurple'] += 1;
|
||||||
|
nextReward = 130;
|
||||||
|
}
|
||||||
|
if (user.loginIncentives >= 130) {
|
||||||
|
inc['items.food.Chocolate'] += 3;
|
||||||
|
inc['items.food.CottonCandyBlue'] += 3;
|
||||||
|
inc['items.food.CottonCandyPink'] += 3;
|
||||||
|
inc['items.food.Fish'] += 3;
|
||||||
|
inc['items.food.Honey'] += 3;
|
||||||
|
inc['items.food.Meat'] += 3;
|
||||||
|
inc['items.food.Milk'] += 3;
|
||||||
|
inc['items.food.Potatoe'] += 3;
|
||||||
|
inc['items.food.RottenMeat'] += 3;
|
||||||
|
inc['items.food.Strawberry'] += 3;
|
||||||
|
}
|
||||||
|
if (user.loginIncentives >= 135) {
|
||||||
|
inc['items.hatchingPotions.RoyalPurple'] += 1;
|
||||||
|
nextReward = 140;
|
||||||
|
}
|
||||||
|
if (user.loginIncentives >= 140) {
|
||||||
|
set['items.gear.owned.weapon_special_skeletonKey'] = true;
|
||||||
|
set['items.gear.owned.shield_special_lootBag'] = true;
|
||||||
|
nextReward = 145;
|
||||||
|
}
|
||||||
|
if (user.loginIncentives >= 145) {
|
||||||
|
inc['items.hatchingPotions.RoyalPurple'] += 1;
|
||||||
|
nextReward = 150;
|
||||||
|
}
|
||||||
|
if (user.loginIncentives >= 150) {
|
||||||
|
set['items.gear.owned.head_special_clandestineCowl'] = true;
|
||||||
|
set['items.gear.owned.armor_special_sneakthiefRobes'] = true;
|
||||||
|
nextReward = 155;
|
||||||
|
}
|
||||||
|
if (user.loginIncentives > 155) {
|
||||||
|
set.loginIncentives = 155;
|
||||||
|
nextReward = 160;
|
||||||
|
}
|
||||||
|
|
||||||
|
var push = {
|
||||||
|
'notifications': {
|
||||||
|
'type': 'LOGIN_INCENTIVE',
|
||||||
|
'data': {
|
||||||
|
'nextRewardAt': nextReward,
|
||||||
|
'rewardKey': [
|
||||||
|
'shop_armoire',
|
||||||
|
],
|
||||||
|
'rewardText': common.i18n.t('checkInRewards', language),
|
||||||
|
'reward': [],
|
||||||
|
'message': common.i18n.t('backloggedCheckInRewards', language),
|
||||||
|
},
|
||||||
|
'id': common.uuid(),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
dbUsers.update({_id: user._id}, {$set:set, $push:push, $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;
|
||||||
114
migrations/20170616_achievements.js
Normal file
114
migrations/20170616_achievements.js
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
var migrationName = '20170616_achievements';
|
||||||
|
var authorName = 'Sabe'; // in case script author needs to know when their ...
|
||||||
|
var authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; //... own data is done
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Updates to achievements for June 16, 2017 biweekly merge
|
||||||
|
* 1. Multiply various collection quest achievements based on difficulty reduction
|
||||||
|
* 2. Award Joined Challenge achievement to those who should have it already
|
||||||
|
*/
|
||||||
|
|
||||||
|
import monk from 'monk';
|
||||||
|
|
||||||
|
var connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
|
||||||
|
var dbUsers = monk(connectionString).get('users', { castIds: false });
|
||||||
|
|
||||||
|
function processUsers(lastId) {
|
||||||
|
// specify a query to limit the affected users (empty for all users):
|
||||||
|
var query = {
|
||||||
|
$or: [
|
||||||
|
{'achievements.quests.dilatoryDistress1': {$gt:0}},
|
||||||
|
{'achievements.quests.egg': {$gt:0}},
|
||||||
|
{'achievements.quests.goldenknight1': {$gt:0}},
|
||||||
|
{'achievements.quests.moonstone1': {$gt:0}},
|
||||||
|
{'achievements.quests.vice2': {$gt:0}},
|
||||||
|
{'achievements.challenges': {$exists: true, $ne: []}},
|
||||||
|
{'challenges': {$exists: true, $ne: []}},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
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):
|
||||||
|
'achievements',
|
||||||
|
'challenges',
|
||||||
|
],
|
||||||
|
})
|
||||||
|
.then(updateUsers)
|
||||||
|
.catch(function (err) {
|
||||||
|
console.log(err);
|
||||||
|
return exiting(1, 'ERROR! ' + err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var progressCount = 1000;
|
||||||
|
var count = 0;
|
||||||
|
|
||||||
|
function updateUsers (users) {
|
||||||
|
if (!users || users.length === 0) {
|
||||||
|
console.warn('All appropriate users found and modified.');
|
||||||
|
displayData();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var userPromises = users.map(updateUser);
|
||||||
|
var lastUser = users[users.length - 1];
|
||||||
|
|
||||||
|
return Promise.all(userPromises)
|
||||||
|
.then(function () {
|
||||||
|
processUsers(lastUser._id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateUser (user) {
|
||||||
|
count++;
|
||||||
|
var set = {'migration': migrationName};
|
||||||
|
|
||||||
|
if (user.challenges.length > 0 || user.achievements.challenges.length > 0) {
|
||||||
|
set['achievements.joinedChallenge'] = true;
|
||||||
|
}
|
||||||
|
if (user.achievements.quests.dilatoryDistress1) {
|
||||||
|
set['achievements.quests.dilatoryDistress1'] = Math.ceil(user.achievements.quests.dilatoryDistress1 * 1.25);
|
||||||
|
}
|
||||||
|
if (user.achievements.quests.egg) {
|
||||||
|
set['achievements.quests.egg'] = Math.ceil(user.achievements.quests.egg * 2.5);
|
||||||
|
}
|
||||||
|
if (user.achievements.quests.goldenknight1) {
|
||||||
|
set['achievements.quests.goldenknight1'] = user.achievements.quests.goldenknight1 * 5;
|
||||||
|
}
|
||||||
|
if (user.achievements.quests.moonstone1) {
|
||||||
|
set['achievements.quests.moonstone1'] = user.achievements.quests.moonstone1 * 5;
|
||||||
|
}
|
||||||
|
if (user.achievements.quests.vice2) {
|
||||||
|
set['achievements.quests.vice2'] = Math.ceil(user.achievements.quests.vice2 * 1.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
@@ -6,6 +6,12 @@
|
|||||||
|
|
||||||
// Due to some big user profiles it needs more RAM than is allowed by default by v8 (arounf 1.7GB).
|
// Due to some big user profiles it needs more RAM than is allowed by default by v8 (arounf 1.7GB).
|
||||||
// Run the script with --max-old-space-size=4096 to allow up to 4GB of RAM
|
// Run the script with --max-old-space-size=4096 to allow up to 4GB of RAM
|
||||||
|
|
||||||
|
// IMPORTANT NOTE: this migration was written when we were using version 3 of lodash.
|
||||||
|
// We've now upgraded to lodash v4 but the code used in this migration has not been
|
||||||
|
// adapted to work with it. Before this migration is used again any lodash method should
|
||||||
|
// be checked for compatibility against the v4 changelog and changed if necessary.
|
||||||
|
// https://github.com/lodash/lodash/wiki/Changelog#v400
|
||||||
console.log('Starting migrations/api_v3/challenges.js.');
|
console.log('Starting migrations/api_v3/challenges.js.');
|
||||||
|
|
||||||
require('babel-register');
|
require('babel-register');
|
||||||
|
|||||||
@@ -9,6 +9,12 @@
|
|||||||
// Run the script with --max-old-space-size=4096 to allow up to 4GB of RAM
|
// Run the script with --max-old-space-size=4096 to allow up to 4GB of RAM
|
||||||
console.log('Starting migrations/api_v3/challengesMembers.js.');
|
console.log('Starting migrations/api_v3/challengesMembers.js.');
|
||||||
|
|
||||||
|
// IMPORTANT NOTE: this migration was written when we were using version 3 of lodash.
|
||||||
|
// We've now upgraded to lodash v4 but the code used in this migration has not been
|
||||||
|
// adapted to work with it. Before this migration is used again any lodash method should
|
||||||
|
// be checked for compatibility against the v4 changelog and changed if necessary.
|
||||||
|
// https://github.com/lodash/lodash/wiki/Changelog#v400
|
||||||
|
|
||||||
require('babel-register');
|
require('babel-register');
|
||||||
require('babel-polyfill');
|
require('babel-polyfill');
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,12 @@
|
|||||||
// Run the script with --max-old-space-size=4096 to allow up to 4GB of RAM
|
// Run the script with --max-old-space-size=4096 to allow up to 4GB of RAM
|
||||||
console.log('Starting migrations/api_v3/coupons.js.');
|
console.log('Starting migrations/api_v3/coupons.js.');
|
||||||
|
|
||||||
|
// IMPORTANT NOTE: this migration was written when we were using version 3 of lodash.
|
||||||
|
// We've now upgraded to lodash v4 but the code used in this migration has not been
|
||||||
|
// adapted to work with it. Before this migration is used again any lodash method should
|
||||||
|
// be checked for compatibility against the v4 changelog and changed if necessary.
|
||||||
|
// https://github.com/lodash/lodash/wiki/Changelog#v400
|
||||||
|
|
||||||
require('babel-register');
|
require('babel-register');
|
||||||
require('babel-polyfill');
|
require('babel-polyfill');
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,12 @@
|
|||||||
// Run the script with --max-old-space-size=4096 to allow up to 4GB of RAM
|
// Run the script with --max-old-space-size=4096 to allow up to 4GB of RAM
|
||||||
console.log('Starting migrations/api_v3/unsubscriptions.js.');
|
console.log('Starting migrations/api_v3/unsubscriptions.js.');
|
||||||
|
|
||||||
|
// IMPORTANT NOTE: this migration was written when we were using version 3 of lodash.
|
||||||
|
// We've now upgraded to lodash v4 but the code used in this migration has not been
|
||||||
|
// adapted to work with it. Before this migration is used again any lodash method should
|
||||||
|
// be checked for compatibility against the v4 changelog and changed if necessary.
|
||||||
|
// https://github.com/lodash/lodash/wiki/Changelog#v400
|
||||||
|
|
||||||
require('babel-register');
|
require('babel-register');
|
||||||
require('babel-polyfill');
|
require('babel-polyfill');
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,12 @@
|
|||||||
// Run the script with --max-old-space-size=4096 to allow up to 4GB of RAM
|
// Run the script with --max-old-space-size=4096 to allow up to 4GB of RAM
|
||||||
console.log('Starting migrations/api_v3/groups.js.');
|
console.log('Starting migrations/api_v3/groups.js.');
|
||||||
|
|
||||||
|
// IMPORTANT NOTE: this migration was written when we were using version 3 of lodash.
|
||||||
|
// We've now upgraded to lodash v4 but the code used in this migration has not been
|
||||||
|
// adapted to work with it. Before this migration is used again any lodash method should
|
||||||
|
// be checked for compatibility against the v4 changelog and changed if necessary.
|
||||||
|
// https://github.com/lodash/lodash/wiki/Changelog#v400
|
||||||
|
|
||||||
require('babel-register');
|
require('babel-register');
|
||||||
require('babel-polyfill');
|
require('babel-polyfill');
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,12 @@
|
|||||||
// Run the script with --max-old-space-size=4096 to allow up to 4GB of RAM
|
// Run the script with --max-old-space-size=4096 to allow up to 4GB of RAM
|
||||||
console.log('Starting migrations/api_v3/users.js.');
|
console.log('Starting migrations/api_v3/users.js.');
|
||||||
|
|
||||||
|
// IMPORTANT NOTE: this migration was written when we were using version 3 of lodash.
|
||||||
|
// We've now upgraded to lodash v4 but the code used in this migration has not been
|
||||||
|
// adapted to work with it. Before this migration is used again any lodash method should
|
||||||
|
// be checked for compatibility against the v4 changelog and changed if necessary.
|
||||||
|
// https://github.com/lodash/lodash/wiki/Changelog#v400
|
||||||
|
|
||||||
require('babel-register');
|
require('babel-register');
|
||||||
require('babel-polyfill');
|
require('babel-polyfill');
|
||||||
|
|
||||||
|
|||||||
47
migrations/challenges/sync-all-challenges.js
Normal file
47
migrations/challenges/sync-all-challenges.js
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import Bluebird from 'Bluebird';
|
||||||
|
|
||||||
|
import { model as Challenges } from '../../website/server/models/challenge';
|
||||||
|
import { model as User } from '../../website/server/models/user';
|
||||||
|
|
||||||
|
async function syncChallengeToMembers (challenges) {
|
||||||
|
let challengSyncPromises = challenges.map(async function (challenge) {
|
||||||
|
let users = await User.find({
|
||||||
|
// _id: '',
|
||||||
|
challenges: challenge._id,
|
||||||
|
}).exec();
|
||||||
|
|
||||||
|
let promises = [];
|
||||||
|
users.forEach(function (user) {
|
||||||
|
promises.push(challenge.syncToUser(user));
|
||||||
|
promises.push(challenge.save());
|
||||||
|
promises.push(user.save());
|
||||||
|
});
|
||||||
|
|
||||||
|
return Bluebird.all(promises);
|
||||||
|
});
|
||||||
|
|
||||||
|
return await Bluebird.all(challengSyncPromises);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function syncChallenges (lastChallengeDate) {
|
||||||
|
let query = {
|
||||||
|
// _id: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
if (lastChallengeDate) {
|
||||||
|
query.createdOn = { $lte: lastChallengeDate };
|
||||||
|
}
|
||||||
|
|
||||||
|
let challengesFound = await Challenges.find(query)
|
||||||
|
.limit(10)
|
||||||
|
.sort('-createdAt')
|
||||||
|
.exec();
|
||||||
|
|
||||||
|
let syncedChallenges = await syncChallengeToMembers(challengesFound)
|
||||||
|
.catch(reason => console.error(reason));
|
||||||
|
let lastChallenge = challengesFound[challengesFound.length - 1];
|
||||||
|
if (lastChallenge) syncChallenges(lastChallenge.createdAt);
|
||||||
|
return syncedChallenges;
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = syncChallenges;
|
||||||
@@ -7,6 +7,6 @@
|
|||||||
|
|
||||||
db.users.find().forEach(function(user){
|
db.users.find().forEach(function(user){
|
||||||
user.tasks = user.habits.concat(user.dailys).concat(user.todos).concat(user.rewards);
|
user.tasks = user.habits.concat(user.dailys).concat(user.todos).concat(user.rewards);
|
||||||
var found = _.any(user.tasks, {text: ""})
|
var found = _.some(user.tasks, {text: ""})
|
||||||
if (found) printjson({id:user._id, auth:user.auth});
|
if (found) printjson({id:user._id, auth:user.auth});
|
||||||
})
|
})
|
||||||
40
migrations/groups/add-unlimited-subscription.js
Normal file
40
migrations/groups/add-unlimited-subscription.js
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
var migrationName = 'AddUnlimitedSubscription';
|
||||||
|
var authorName = 'TheHollidayInn'; // in case script author needs to know when their ...
|
||||||
|
var authorUuid = ''; //... own data is done
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This migrations will add a free subscription to a specified group
|
||||||
|
*/
|
||||||
|
import moment from 'moment';
|
||||||
|
|
||||||
|
import { model as Group } from '../../website/server/models/group';
|
||||||
|
|
||||||
|
// @TODO: this should probably be a GroupManager library method
|
||||||
|
async function addUnlimitedSubscription (groupId, dateTerminated) {
|
||||||
|
let group = await Group.findById(groupId);
|
||||||
|
|
||||||
|
group.purchased.plan.customerId = "group-unlimited";
|
||||||
|
group.purchased.plan.dateCreated = new Date();
|
||||||
|
group.purchased.plan.dateUpdated = new Date();
|
||||||
|
group.purchased.plan.paymentMethod = "Group Unlimited";
|
||||||
|
group.purchased.plan.planId = "group_monthly";
|
||||||
|
group.purchased.plan.dateTerminated = null;
|
||||||
|
if (dateTerminated) {
|
||||||
|
let dateToEnd = moment(dateTerminated).toDate();
|
||||||
|
group.purchased.plan.dateTerminated = dateToEnd;
|
||||||
|
}
|
||||||
|
// group.purchased.plan.owner = ObjectId();
|
||||||
|
group.purchased.plan.subscriptionId = "";
|
||||||
|
|
||||||
|
return group.save();
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = async function addUnlimitedSubscriptionCreator () {
|
||||||
|
let groupId = process.argv[2];
|
||||||
|
|
||||||
|
if (!groupId) throw Error('Group ID is required');
|
||||||
|
|
||||||
|
let dateTerminated = process.argv[3];
|
||||||
|
|
||||||
|
let result = await addUnlimitedSubscription(groupId, dateTerminated);
|
||||||
|
};
|
||||||
32
migrations/groups/create-group.js
Normal file
32
migrations/groups/create-group.js
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import Bluebird from 'bluebird';
|
||||||
|
|
||||||
|
import { model as Group } from '../../website/server/models/group';
|
||||||
|
import { model as User } from '../../website/server/models/user';
|
||||||
|
|
||||||
|
// @TODO: this should probably be a GroupManager library method
|
||||||
|
async function createGroup (name, privacy, type, leaderId) {
|
||||||
|
let user = await User.findById(leaderId);
|
||||||
|
|
||||||
|
let group = new Group({
|
||||||
|
name,
|
||||||
|
privacy,
|
||||||
|
type,
|
||||||
|
});
|
||||||
|
|
||||||
|
group.leader = user._id;
|
||||||
|
user.guilds.push(group._id);
|
||||||
|
|
||||||
|
return Bluebird.all([group.save(), user.save()]);
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = async function groupCreator () {
|
||||||
|
let name = process.argv[2];
|
||||||
|
let privacy = process.argv[3];
|
||||||
|
let type = process.argv[4];
|
||||||
|
let leaderId = process.argv[5];
|
||||||
|
|
||||||
|
let result = await createGroup(name, privacy, type, leaderId)
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
46
migrations/groups/habitrpg-jackalopes.js
Normal file
46
migrations/groups/habitrpg-jackalopes.js
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
var migrationName = 'Jackalopes for Unlimited Subscribers';
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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 User } from '../../website/server/models/user';
|
||||||
|
import * as payments from '../../website/server/libs/payments';
|
||||||
|
|
||||||
|
async function handOutJackalopes () {
|
||||||
|
let promises = [];
|
||||||
|
let cursor = User.find({
|
||||||
|
'purchased.plan.customerId':'habitrpg',
|
||||||
|
}).cursor();
|
||||||
|
|
||||||
|
cursor.on('data', async function(user) {
|
||||||
|
console.log('User: ' + user._id);
|
||||||
|
|
||||||
|
let groupList = [];
|
||||||
|
if (user.party._id) groupList.push(user.party._id);
|
||||||
|
groupList = groupList.concat(user.guilds);
|
||||||
|
|
||||||
|
let subscribedGroup =
|
||||||
|
await Group.findOne({
|
||||||
|
'_id': {$in: groupList},
|
||||||
|
'purchased.plan.planId': 'group_monthly',
|
||||||
|
'purchased.plan.dateTerminated': null,
|
||||||
|
},
|
||||||
|
{'_id':1}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (subscribedGroup) {
|
||||||
|
User.update({'_id':user._id},{$set:{'items.mounts.Jackalope-RoyalPurple':true}}).exec();
|
||||||
|
promises.push(user.save());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
cursor.on('close', async function() {
|
||||||
|
console.log('done');
|
||||||
|
return await Bluebird.all(promises);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = handOutJackalopes;
|
||||||
33
migrations/groups/update-groups-with-group-plans.js
Normal file
33
migrations/groups/update-groups-with-group-plans.js
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
var migrationName = 'ResyncGroupPlanMembers';
|
||||||
|
var authorName = 'TheHollidayInn'; // in case script author needs to know when their ...
|
||||||
|
var authorUuid = ''; //... own data is done
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This migrations will iterate through all groups with a group plan a subscription and resync the free
|
||||||
|
* subscription to all members
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Bluebird from 'bluebird';
|
||||||
|
|
||||||
|
import { model as Group } from '../../website/server/models/group';
|
||||||
|
import * as payments from '../../website/server/libs/payments';
|
||||||
|
|
||||||
|
async function updateGroupsWithGroupPlans () {
|
||||||
|
let cursor = Group.find({
|
||||||
|
'purchased.plan.planId': 'group_monthly',
|
||||||
|
'purchased.plan.dateTerminated': null,
|
||||||
|
}).cursor();
|
||||||
|
|
||||||
|
let promises = [];
|
||||||
|
|
||||||
|
cursor.on('data', function(group) {
|
||||||
|
promises.push(payments.addSubscriptionToGroupUsers(group));
|
||||||
|
promises.push(group.save())
|
||||||
|
});
|
||||||
|
|
||||||
|
cursor.on('close', async function() {
|
||||||
|
return await Bluebird.all(promises);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = updateGroupsWithGroupPlans;
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
// EMAIL="x@y.com" node ./migrations/manual_password_reset.js
|
// EMAIL="x@y.com" node ./migrations/manual_password_reset.js
|
||||||
// Be sure to have PRODUCTION_DB in your config.json
|
// Be sure to have PRODUCTION_DB in your config.json
|
||||||
|
|
||||||
|
// IMPORTANT: this script isn't updated to use the new password encryption that uses bcrypt
|
||||||
|
// using it will break accounts and should not be used until upgraded
|
||||||
|
|
||||||
var nconf = require('nconf'),
|
var nconf = require('nconf'),
|
||||||
path = require('path');
|
path = require('path');
|
||||||
nconf.argv().env().file('user', path.join(path.resolve(__dirname, '../config.json')));
|
nconf.argv().env().file('user', path.join(path.resolve(__dirname, '../config.json')));
|
||||||
|
|||||||
@@ -1,21 +1,24 @@
|
|||||||
require("babel-register");
|
require("babel-register");
|
||||||
require("babel-polyfill");
|
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 () {
|
||||||
var nconf = require('nconf');
|
var nconf = require('nconf');
|
||||||
var mongoose = require('mongoose');
|
var mongoose = require('mongoose');
|
||||||
var Bluebird = require('bluebird');
|
var Bluebird = require('bluebird');
|
||||||
var setupNconf = require('../website/server/libs/setupNconf');
|
var setupNconf = require('../website/server/libs/setupNconf');
|
||||||
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
|
||||||
require('../website/server/server'); // eslint-disable-line global-require
|
require('../website/server/server'); // eslint-disable-line global-require
|
||||||
}
|
}
|
||||||
setUpServer();
|
setUpServer();
|
||||||
|
|
||||||
// Replace this with your migration
|
// Replace this with your migration
|
||||||
var processUsers = require('./new_stuff');
|
var processUsers = require('./groups/update-groups-with-group-plans');
|
||||||
processUsers();
|
processUsers()
|
||||||
|
.catch(function (err) {
|
||||||
|
console.log(err)
|
||||||
|
})
|
||||||
@@ -2,7 +2,7 @@ var _id = '';
|
|||||||
var update = {
|
var update = {
|
||||||
$addToSet: {
|
$addToSet: {
|
||||||
'purchased.plan.mysteryItems':{
|
'purchased.plan.mysteryItems':{
|
||||||
$each:['shield_mystery_201701','eyewear_mystery_201701']
|
$each:['body_mystery_201706','back_mystery_201706']
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
var migrationName = '20170201_takeThis.js'; // Update per month
|
var migrationName = '20170502_takeThis.js'; // Update per month
|
||||||
var authorName = 'Sabe'; // in case script author needs to know when their ...
|
var authorName = 'Sabe'; // in case script author needs to know when their ...
|
||||||
var authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; //... own data is done
|
var authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; //... own data is done
|
||||||
|
|
||||||
@@ -14,7 +14,7 @@ function processUsers(lastId) {
|
|||||||
// specify a query to limit the affected users (empty for all users):
|
// specify a query to limit the affected users (empty for all users):
|
||||||
var query = {
|
var query = {
|
||||||
'migration':{$ne:migrationName},
|
'migration':{$ne:migrationName},
|
||||||
'challenges':{$in:['b1d436b5-c784-42e3-9b07-7072479a6f8e']} // Update per month
|
'challenges':{$in:['69999331-d4ea-45a0-8c3f-f725d22b56c8']} // Update per month
|
||||||
};
|
};
|
||||||
|
|
||||||
if (lastId) {
|
if (lastId) {
|
||||||
|
|||||||
5449
npm-shrinkwrap.json
generated
5449
npm-shrinkwrap.json
generated
File diff suppressed because it is too large
Load Diff
120
package.json
120
package.json
@@ -1,22 +1,24 @@
|
|||||||
{
|
{
|
||||||
"name": "habitica",
|
"name": "habitica",
|
||||||
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
|
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
|
||||||
"version": "3.75.3",
|
"version": "3.98.0",
|
||||||
"main": "./website/server/index.js",
|
"main": "./website/server/index.js",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@slack/client": "3.6.0",
|
"@slack/client": "^3.8.1",
|
||||||
"accepts": "^1.3.2",
|
"accepts": "^1.3.2",
|
||||||
"amazon-payments": "0.0.4",
|
"amazon-payments": "0.0.4",
|
||||||
"amplitude": "^2.0.3",
|
"amplitude": "^2.0.3",
|
||||||
"apidoc": "^0.16.0",
|
"apidoc": "^0.17.5",
|
||||||
"apn": "^1.7.6",
|
"apn": "^1.7.6",
|
||||||
"async": "^1.5.0",
|
"async": "^1.5.0",
|
||||||
"autoprefixer": "^6.4.0",
|
"autoprefixer": "^6.4.0",
|
||||||
"aws-sdk": "^2.0.25",
|
"aws-sdk": "^2.0.25",
|
||||||
"axios": "^0.15.3",
|
"axios": "^0.16.0",
|
||||||
"babel-core": "^6.0.0",
|
"babel-core": "^6.0.0",
|
||||||
|
"babel-eslint": "^7.2.3",
|
||||||
"babel-loader": "^6.0.0",
|
"babel-loader": "^6.0.0",
|
||||||
"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-transform-async-to-module-method": "^6.8.0",
|
"babel-plugin-transform-async-to-module-method": "^6.8.0",
|
||||||
"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",
|
||||||
@@ -28,22 +30,25 @@
|
|||||||
"bcrypt": "^1.0.2",
|
"bcrypt": "^1.0.2",
|
||||||
"bluebird": "^3.3.5",
|
"bluebird": "^3.3.5",
|
||||||
"body-parser": "^1.15.0",
|
"body-parser": "^1.15.0",
|
||||||
|
"bootstrap": "^4.0.0-alpha.6",
|
||||||
|
"bootstrap-vue": "^0.15.8",
|
||||||
"bower": "~1.3.12",
|
"bower": "~1.3.12",
|
||||||
"browserify": "~12.0.1",
|
"browserify": "~12.0.1",
|
||||||
"compression": "^1.6.1",
|
"compression": "^1.6.1",
|
||||||
"connect-ratelimit": "0.0.7",
|
"connect-ratelimit": "0.0.7",
|
||||||
"cookie-session": "^1.2.0",
|
"cookie-session": "^1.2.0",
|
||||||
"coupon-code": "^0.4.5",
|
"coupon-code": "^0.4.5",
|
||||||
"css-loader": "^0.23.1",
|
"css-loader": "^0.28.0",
|
||||||
"csv-stringify": "^1.0.2",
|
"csv-stringify": "^1.0.2",
|
||||||
"cwait": "^1.0.0",
|
"cwait": "^1.0.0",
|
||||||
"domain-middleware": "~0.1.0",
|
"domain-middleware": "~0.1.0",
|
||||||
"estraverse": "^4.1.1",
|
"estraverse": "^4.1.1",
|
||||||
"express": "~4.14.0",
|
"express": "~4.14.0",
|
||||||
|
"express-basic-auth": "^1.0.1",
|
||||||
"express-csv": "~0.6.0",
|
"express-csv": "~0.6.0",
|
||||||
"express-validator": "^2.18.0",
|
"express-validator": "^2.18.0",
|
||||||
"extract-text-webpack-plugin": "^1.0.1",
|
"extract-text-webpack-plugin": "^2.0.0-rc.3",
|
||||||
"file-loader": "^0.8.4",
|
"file-loader": "^0.10.0",
|
||||||
"glob": "^4.3.5",
|
"glob": "^4.3.5",
|
||||||
"got": "^6.1.1",
|
"got": "^6.1.1",
|
||||||
"grunt": "~0.4.1",
|
"grunt": "~0.4.1",
|
||||||
@@ -54,7 +59,7 @@
|
|||||||
"grunt-contrib-stylus": "~0.20.0",
|
"grunt-contrib-stylus": "~0.20.0",
|
||||||
"grunt-contrib-uglify": "~0.6.0",
|
"grunt-contrib-uglify": "~0.6.0",
|
||||||
"grunt-contrib-watch": "~0.6.1",
|
"grunt-contrib-watch": "~0.6.1",
|
||||||
"grunt-hashres": "~0.4.1",
|
"grunt-hashres": "habitrpg/grunt-hashres#v0.4.2",
|
||||||
"gulp": "^3.9.0",
|
"gulp": "^3.9.0",
|
||||||
"gulp-babel": "^6.1.2",
|
"gulp-babel": "^6.1.2",
|
||||||
"gulp-grunt": "^0.5.2",
|
"gulp-grunt": "^0.5.2",
|
||||||
@@ -68,62 +73,64 @@
|
|||||||
"image-size": "~0.3.2",
|
"image-size": "~0.3.2",
|
||||||
"in-app-purchase": "^1.1.6",
|
"in-app-purchase": "^1.1.6",
|
||||||
"jade": "~1.11.0",
|
"jade": "~1.11.0",
|
||||||
"jquery": "https://registry.npmjs.org/jquery/-/jquery-3.1.1.tgz",
|
"jquery": "^3.1.1",
|
||||||
"js2xmlparser": "~1.0.0",
|
"js2xmlparser": "~1.0.0",
|
||||||
"json-loader": "^0.5.4",
|
"lodash": "^4.17.4",
|
||||||
"less": "^2.7.1",
|
|
||||||
"less-loader": "^2.2.3",
|
|
||||||
"lodash": "^3.10.1",
|
|
||||||
"lodash.pickby": "^4.2.0",
|
|
||||||
"lodash.setwith": "^4.2.0",
|
|
||||||
"merge-stream": "^1.0.0",
|
"merge-stream": "^1.0.0",
|
||||||
"method-override": "^2.3.5",
|
"method-override": "^2.3.5",
|
||||||
"moment": "^2.13.0",
|
"moment": "^2.13.0",
|
||||||
"mongoose": "^4.7.1",
|
"moment-recur": "habitrpg/moment-recur#v1.0.6",
|
||||||
|
"mongoose": "^4.8.6",
|
||||||
"mongoose-id-autoinc": "~2013.7.14-4",
|
"mongoose-id-autoinc": "~2013.7.14-4",
|
||||||
"morgan": "^1.7.0",
|
"morgan": "^1.7.0",
|
||||||
"nconf": "~0.8.2",
|
"nconf": "~0.8.2",
|
||||||
"nib": "^1.1.0",
|
"nib": "^1.1.0",
|
||||||
"node-gcm": "^0.14.4",
|
"node-gcm": "^0.14.4",
|
||||||
|
"node-sass": "^4.5.0",
|
||||||
"nodemailer": "^2.3.2",
|
"nodemailer": "^2.3.2",
|
||||||
"object-path": "^0.9.2",
|
"object-path": "^0.9.2",
|
||||||
"ora": "^0.2.0",
|
"ora": "^1.1.0",
|
||||||
"pageres": "^4.1.1",
|
"pageres": "^4.1.1",
|
||||||
"passport": "^0.3.2",
|
"passport": "^0.3.2",
|
||||||
"passport-facebook": "^2.0.0",
|
"passport-facebook": "^2.0.0",
|
||||||
"passport-google-oauth20": "1.0.0",
|
"passport-google-oauth20": "1.0.0",
|
||||||
"paypal-ipn": "3.0.0",
|
"paypal-ipn": "3.0.0",
|
||||||
"paypal-rest-sdk": "^1.2.1",
|
"paypal-rest-sdk": "^1.2.1",
|
||||||
"postcss-easy-import": "^1.0.1",
|
"postcss-easy-import": "^2.0.0",
|
||||||
"pretty-data": "^0.40.0",
|
"pretty-data": "^0.40.0",
|
||||||
"ps-tree": "^1.0.0",
|
"ps-tree": "^1.0.0",
|
||||||
"pug": "^2.0.0-beta6",
|
"pug": "^2.0.0-beta.12",
|
||||||
"push-notify": "habitrpg/push-notify#v1.2.0",
|
"push-notify": "habitrpg/push-notify#v1.2.0",
|
||||||
"pusher": "^1.3.0",
|
"pusher": "^1.3.0",
|
||||||
"request": "~2.74.0",
|
"request": "~2.74.0",
|
||||||
"rimraf": "^2.4.3",
|
"rimraf": "^2.4.3",
|
||||||
"run-sequence": "^1.1.4",
|
"run-sequence": "^1.1.4",
|
||||||
"s3-upload-stream": "^1.0.6",
|
"s3-upload-stream": "^1.0.6",
|
||||||
"semantic-ui-less": "~2.2.4",
|
"sass-loader": "^6.0.2",
|
||||||
"serve-favicon": "^2.3.0",
|
"serve-favicon": "^2.3.0",
|
||||||
"shelljs": "^0.6.0",
|
"shelljs": "^0.7.6",
|
||||||
"stripe": "^4.2.0",
|
"stripe": "^4.2.0",
|
||||||
"superagent": "^1.8.3",
|
"superagent": "^3.4.3",
|
||||||
|
"svg-inline-loader": "^0.7.1",
|
||||||
|
"svg-url-loader": "^2.0.2",
|
||||||
|
"svgo-loader": "^1.2.1",
|
||||||
"universal-analytics": "~0.3.2",
|
"universal-analytics": "~0.3.2",
|
||||||
"url-loader": "^0.5.7",
|
"url-loader": "^0.5.7",
|
||||||
"useragent": "2.1.9",
|
"useragent": "^2.1.9",
|
||||||
"uuid": "^2.0.1",
|
"uuid": "^3.0.1",
|
||||||
"validator": "^4.9.0",
|
"validator": "^4.9.0",
|
||||||
"vinyl-buffer": "^1.0.0",
|
"vinyl-buffer": "^1.0.0",
|
||||||
"vinyl-source-stream": "^1.1.0",
|
"vinyl-source-stream": "^1.1.0",
|
||||||
"vue": "^2.1.0",
|
"vue": "^2.1.0",
|
||||||
"vue-hot-reload-api": "^1.2.0",
|
"vue-loader": "^11.0.0",
|
||||||
"vue-loader": "^10.0.0",
|
"vue-mugen-scroll": "^0.2.1",
|
||||||
"vue-router": "^2.0.0-rc.5",
|
"vue-router": "^2.0.0-rc.5",
|
||||||
"vue-template-compiler": "^2.1.0",
|
"vue-style-loader": "^3.0.0",
|
||||||
"webpack": "^1.12.2",
|
"vue-template-compiler": "^2.1.10",
|
||||||
"webpack-merge": "^0.8.3",
|
"webpack": "^2.2.1",
|
||||||
|
"webpack-merge": "^4.0.0",
|
||||||
"winston": "^2.1.0",
|
"winston": "^2.1.0",
|
||||||
|
"winston-loggly-bulk": "^1.4.2",
|
||||||
"xml2js": "^0.4.4"
|
"xml2js": "^0.4.4"
|
||||||
},
|
},
|
||||||
"private": true,
|
"private": true,
|
||||||
@@ -133,15 +140,15 @@
|
|||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"lint": "eslint --ext .js,.vue .",
|
"lint": "eslint --ext .js,.vue .",
|
||||||
"test": "npm run lint && gulp test && npm run client:unit",
|
"test": "npm run lint && gulp test && npm run client:unit && gulp apidoc",
|
||||||
"test:build": "gulp test:prepare:build",
|
"test:build": "gulp test:prepare:build",
|
||||||
"test:api-v3": "gulp test:api-v3",
|
"test:api-v3": "gulp test:api-v3",
|
||||||
"test:api-v3:unit": "gulp test:api-v3:unit",
|
"test:api-v3:unit": "gulp test:api-v3:unit",
|
||||||
"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:sanity": "mocha test/sanity --recursive",
|
"test:sanity": "istanbul cover --dir coverage/sanity --report lcovonly node_modules/mocha/bin/_mocha -- test/sanity --recursive",
|
||||||
"test:common": "mocha test/common --recursive",
|
"test:common": "istanbul cover --dir coverage/common --report lcovonly node_modules/mocha/bin/_mocha -- test/common --recursive",
|
||||||
"test:content": "mocha test/content --recursive",
|
"test:content": "istanbul cover --dir coverage/content --report lcovonly node_modules/mocha/bin/_mocha -- test/content --recursive",
|
||||||
"test:karma": "karma start test/client-old/spec/karma.conf.js --single-run",
|
"test:karma": "karma start test/client-old/spec/karma.conf.js --single-run",
|
||||||
"test:karma:watch": "karma start test/client-old/spec/karma.conf.js",
|
"test:karma:watch": "karma start test/client-old/spec/karma.conf.js",
|
||||||
"test:prepare:webdriver": "webdriver-manager update",
|
"test:prepare:webdriver": "webdriver-manager update",
|
||||||
@@ -150,39 +157,41 @@
|
|||||||
"test:nodemon": "gulp test:nodemon",
|
"test:nodemon": "gulp test:nodemon",
|
||||||
"coverage": "COVERAGE=true mocha --require register-handlers.js --reporter html-cov > coverage.html; open coverage.html",
|
"coverage": "COVERAGE=true mocha --require register-handlers.js --reporter html-cov > coverage.html; open coverage.html",
|
||||||
"sprites": "gulp sprites:compile",
|
"sprites": "gulp sprites:compile",
|
||||||
"client:dev": "node webpack/dev-server.js",
|
"client:dev": "gulp bootstrap && node webpack/dev-server.js",
|
||||||
"client:build": "node webpack/build.js",
|
"client:build": "gulp bootstrap && node webpack/build.js",
|
||||||
"client:unit": "karma start test/client/unit/karma.conf.js --single-run",
|
"client:unit": "cross-env NODE_ENV=test karma start test/client/unit/karma.conf.js --single-run",
|
||||||
"client:unit:watch": "karma start test/client/unit/karma.conf.js",
|
"client:unit:watch": "cross-env NODE_ENV=test karma start test/client/unit/karma.conf.js",
|
||||||
"client:e2e": "node test/client/e2e/runner.js",
|
"client:e2e": "node test/client/e2e/runner.js",
|
||||||
"client:test": "npm run client:unit && npm run client:e2e",
|
"client:test": "npm run client:unit && npm run client:e2e",
|
||||||
"start": "gulp run:dev",
|
"start": "gulp run:dev",
|
||||||
"postinstall": "bower --config.interactive=false install -f; gulp build; npm run client:build"
|
"postinstall": "bower --config.interactive=false install -f && gulp build && npm run client:build",
|
||||||
|
"apidoc": "gulp apidoc"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"babel-plugin-istanbul": "^4.0.0",
|
||||||
"chai": "^3.4.0",
|
"chai": "^3.4.0",
|
||||||
"chai-as-promised": "^5.1.0",
|
"chai-as-promised": "^5.1.0",
|
||||||
"chalk": "^1.1.3",
|
"chalk": "^1.1.3",
|
||||||
"chromedriver": "^2.21.2",
|
"chromedriver": "^2.27.2",
|
||||||
"connect-history-api-fallback": "^1.1.0",
|
"connect-history-api-fallback": "^1.1.0",
|
||||||
"coveralls": "^2.11.2",
|
"coveralls": "^2.11.2",
|
||||||
"cross-spawn": "^2.1.5",
|
"cross-env": "^4.0.0",
|
||||||
|
"cross-spawn": "^5.0.1",
|
||||||
"csv": "~0.3.6",
|
"csv": "~0.3.6",
|
||||||
"deep-diff": "~0.1.4",
|
"deep-diff": "~0.1.4",
|
||||||
"eslint": "^3.0.0",
|
"eslint": "^3.0.0",
|
||||||
"eslint-config-habitrpg": "^2.0.0",
|
"eslint-config-habitrpg": "^3.0.0",
|
||||||
"eslint-friendly-formatter": "^2.0.5",
|
"eslint-friendly-formatter": "^2.0.5",
|
||||||
"eslint-loader": "^1.3.0",
|
"eslint-loader": "^1.3.0",
|
||||||
"eslint-plugin-html": "^1.3.0",
|
"eslint-plugin-html": "^2.0.0",
|
||||||
"eslint-plugin-mocha": "^4.7.0",
|
"eslint-plugin-mocha": "^4.7.0",
|
||||||
"event-stream": "^3.2.2",
|
"event-stream": "^3.2.2",
|
||||||
"eventsource-polyfill": "^0.9.6",
|
"eventsource-polyfill": "^0.9.6",
|
||||||
"expect.js": "~0.2.0",
|
"expect.js": "~0.2.0",
|
||||||
"grunt-karma": "~0.12.1",
|
"grunt-karma": "~0.12.1",
|
||||||
"http-proxy-middleware": "^0.12.0",
|
"http-proxy-middleware": "^0.17.0",
|
||||||
"inject-loader": "^2.0.1",
|
"inject-loader": "^3.0.0-beta4",
|
||||||
"isparta-loader": "^2.0.0",
|
"istanbul": "^1.1.0-alpha.1",
|
||||||
"istanbul": "^0.3.14",
|
|
||||||
"karma": "^1.3.0",
|
"karma": "^1.3.0",
|
||||||
"karma-babel-preprocessor": "^6.0.1",
|
"karma-babel-preprocessor": "^6.0.1",
|
||||||
"karma-chai-plugins": "~0.6.0",
|
"karma-chai-plugins": "~0.6.0",
|
||||||
@@ -190,28 +199,31 @@
|
|||||||
"karma-mocha": "^0.2.0",
|
"karma-mocha": "^0.2.0",
|
||||||
"karma-mocha-reporter": "^1.1.1",
|
"karma-mocha-reporter": "^1.1.1",
|
||||||
"karma-phantomjs-launcher": "^1.0.0",
|
"karma-phantomjs-launcher": "^1.0.0",
|
||||||
"karma-sinon-chai": "^1.2.0",
|
"karma-sinon-chai": "~1.2.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.24",
|
"karma-spec-reporter": "0.0.24",
|
||||||
"karma-webpack": "^1.7.0",
|
"karma-webpack": "^2.0.2",
|
||||||
"lcov-result-merger": "^1.0.2",
|
"lcov-result-merger": "^1.0.2",
|
||||||
"lolex": "^1.4.0",
|
"lolex": "^1.4.0",
|
||||||
"mocha": "^2.3.3",
|
"mocha": "^3.2.0",
|
||||||
"mongodb": "^2.0.46",
|
"mongodb": "^2.0.46",
|
||||||
"mongoskin": "~2.1.0",
|
"mongoskin": "~2.1.0",
|
||||||
"monk": "^3.1.3",
|
"monk": "^4.0.0",
|
||||||
"nightwatch": "^0.8.18",
|
"nightwatch": "^0.9.12",
|
||||||
"phantomjs-prebuilt": "^2.1.12",
|
"phantomjs-prebuilt": "^2.1.12",
|
||||||
"protractor": "^3.1.1",
|
"protractor": "^3.1.1",
|
||||||
|
"raw-loader": "^0.5.1",
|
||||||
"require-again": "^2.0.0",
|
"require-again": "^2.0.0",
|
||||||
"rewire": "^2.3.3",
|
"rewire": "^2.3.3",
|
||||||
"selenium-server": "2.53.0",
|
"selenium-server": "^3.0.1",
|
||||||
"sinon": "^1.17.2",
|
"sinon": "^1.17.2",
|
||||||
"sinon-chai": "^2.8.0",
|
"sinon-chai": "^2.8.0",
|
||||||
"sinon-stub-promise": "^4.0.0",
|
"sinon-stub-promise": "^4.0.0",
|
||||||
"superagent-defaults": "^0.1.13",
|
"superagent-defaults": "^0.1.13",
|
||||||
"vinyl-transform": "^1.0.0",
|
"vinyl-transform": "^1.0.0",
|
||||||
"webpack-dev-middleware": "^1.4.0",
|
"webpack-bundle-analyzer": "^2.2.1",
|
||||||
"webpack-hot-middleware": "^2.6.0"
|
"webpack-dev-middleware": "^1.10.0",
|
||||||
|
"webpack-hot-middleware": "^2.6.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
1
test/README.md
Normal file
1
test/README.md
Normal file
@@ -0,0 +1 @@
|
|||||||
|
For information about writing and running tests, see [Using Your Local Install to Modify Habitica's Website and API](http://habitica.wikia.com/wiki/Using_Your_Local_Install_to_Modify_Habitica%27s_Website_and_API).
|
||||||
@@ -1,123 +0,0 @@
|
|||||||
# So you want to write API integration tests?
|
|
||||||
|
|
||||||
@TODO rewrite
|
|
||||||
|
|
||||||
That's great! This README will serve as a quick primer for style conventions and practices for these tests.
|
|
||||||
|
|
||||||
## What is this?
|
|
||||||
|
|
||||||
These are integration tests for the Habitica API. They are performed by making HTTP requests to the API's endpoints and asserting on the data that is returned.
|
|
||||||
|
|
||||||
If the javascript looks weird to you, that's because it's written in [ES2015](http://www.ecma-international.org/ecma-262/6.0/) and transpiled by [Babel](https://babeljs.io/docs/learn-es2015/).
|
|
||||||
|
|
||||||
## How to run the tests
|
|
||||||
|
|
||||||
First, install gulp.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ npm install -g gulp
|
|
||||||
```
|
|
||||||
|
|
||||||
To run the api tests, make sure the mongo db is up and running and then type this on the command line:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ gulp test:api-v2
|
|
||||||
```
|
|
||||||
|
|
||||||
It may take a little while to get going, since it requires the web server to start up before the tests can run.
|
|
||||||
|
|
||||||
You can also run a watch command for the api tests. This will allow the tests to re-run automatically when you make changes in the `/test/api/v2/`.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ gulp test:api-v2:watch
|
|
||||||
```
|
|
||||||
|
|
||||||
One caveat. If you have a severe syntax error in your files, the tests may fail to run, but they won't alert you that they are not running. If you ever find your test hanging for a while, run this to get the stackstrace. Once you've fixed the problem, you can resume running the watch comand.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ gulp test:api:safe
|
|
||||||
```
|
|
||||||
|
|
||||||
If you'd like to run the tests individually and inspect the output from the server, in one pane you can run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ gulp test:nodemon
|
|
||||||
```
|
|
||||||
|
|
||||||
And run your tests in another pane:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ mocha path/to/file.js
|
|
||||||
|
|
||||||
# Mark a test with the `.only` attribute
|
|
||||||
$ mocha
|
|
||||||
```
|
|
||||||
|
|
||||||
## Structure
|
|
||||||
|
|
||||||
Each top level route has it's own directory. So, all the routes that begin with `/groups/` live in `/test/api/groups/`.
|
|
||||||
|
|
||||||
Each test should:
|
|
||||||
|
|
||||||
* encompase a single route
|
|
||||||
* begin with the REST parameter for the route
|
|
||||||
* display the full name of the route, swapping out `/` for `_`
|
|
||||||
* end with test.js
|
|
||||||
|
|
||||||
So, for the `POST` route `/groups/:id/leave`, it would be
|
|
||||||
|
|
||||||
```bash
|
|
||||||
POST-groups_id_leave.test.js
|
|
||||||
```
|
|
||||||
|
|
||||||
## Promises
|
|
||||||
|
|
||||||
To mitigate [callback hell](http://callbackhell.com/) :imp:, we've written a helper method to generate a user object that can make http requests that [return promises](https://babeljs.io/docs/learn-es2015/#promises). This makes it very easy to chain together commands. All you need to do to make a subsequent request is return another promise and then call `.then((result) => {})` on the surrounding block, like so:
|
|
||||||
|
|
||||||
```js
|
|
||||||
it('does something', () => {
|
|
||||||
let user;
|
|
||||||
|
|
||||||
return generateUser().then((_user) => { // We return the initial promise so this test can be run asyncronously
|
|
||||||
user = _user;
|
|
||||||
|
|
||||||
return user.post('/groups', {
|
|
||||||
type: 'party',
|
|
||||||
});
|
|
||||||
}).then((party) => { // the result of the promise above is the argument of the function
|
|
||||||
return user.put(`/groups/${party._id}`, {
|
|
||||||
name: 'My party',
|
|
||||||
});
|
|
||||||
}).then((result) => {
|
|
||||||
return user.get('/groups/party');
|
|
||||||
}).then((party) => {
|
|
||||||
expect(party.name).to.eql('My party');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
If the test is simple, you can use the [chai-as-promised](http://chaijs.com/plugins/chai-as-promised) `return expect(somePromise).to.eventually` syntax to make your assertion.
|
|
||||||
|
|
||||||
```js
|
|
||||||
it('makes the party creator the leader automatically', () => {
|
|
||||||
return expect(user.post('/groups', {
|
|
||||||
type: 'party',
|
|
||||||
})).to.eventually.have.deep.property('leader._id', user._id);
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
If the test is checking that the request returns an error, use the `.eventually.be.rejected.and.eql` syntax.
|
|
||||||
|
|
||||||
```js
|
|
||||||
it('returns an error', () => {
|
|
||||||
return expect(user.get('/groups/id-of-a-party-that-user-does-not-belong-to'))
|
|
||||||
.to.eventually.be.rejected.and.eql({
|
|
||||||
code: 404,
|
|
||||||
text: t('messageGroupNotFound'),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## Questions?
|
|
||||||
|
|
||||||
Ask in the [Aspiring Coder's Guild](https://habitica.com/#/options/groups/guilds/68d4a01e-db97-4786-8ee3-05d612c5af6f)!
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
# How to run tests:
|
|
||||||
|
|
||||||
1. `npm test` is equivalent to `gulp test:api-v3` which will run, in order, `gulp lint`, `gulp test:api-v3:unit` and `gulp test:api-v3:integration`. If one of these fails, the whole `npm test` command blocks and fails. Each of these commands can also be run as a standalone command.
|
|
||||||
2. To run the server and the integrations tests in two different terminals (to better inspect the output in the server) run `npm start` in one and `npm test:api-v3:integration:separate-server` in the other
|
|
||||||
@@ -304,5 +304,15 @@ describe('POST /challenges', () => {
|
|||||||
|
|
||||||
await expect(groupLeader.sync()).to.eventually.have.property('challenges').to.include(challenge._id);
|
await expect(groupLeader.sync()).to.eventually.have.property('challenges').to.include(challenge._id);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('awards achievement if this is creator\'s first challenge', async () => {
|
||||||
|
await groupLeader.post('/challenges', {
|
||||||
|
group: group._id,
|
||||||
|
name: 'Test Challenge',
|
||||||
|
shortName: 'TC Label',
|
||||||
|
});
|
||||||
|
groupLeader = await groupLeader.sync();
|
||||||
|
expect(groupLeader.achievements.joinedChallenge).to.be.true;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -123,5 +123,12 @@ describe('POST /challenges/:challengeId/join', () => {
|
|||||||
|
|
||||||
await expect(authorizedUser.get('/tags')).to.eventually.have.length(userTagsLength + 1);
|
await expect(authorizedUser.get('/tags')).to.eventually.have.length(userTagsLength + 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('awards achievement if this is user\'s first challenge', async () => {
|
||||||
|
await authorizedUser.post(`/challenges/${challenge._id}/join`);
|
||||||
|
|
||||||
|
await authorizedUser.sync();
|
||||||
|
expect(authorizedUser.achievements.joinedChallenge).to.be.true;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -117,7 +117,6 @@ describe('POST /challenges/:challengeId/leave', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
expect(testTask).to.not.be.undefined;
|
expect(testTask).to.not.be.undefined;
|
||||||
expect(testTask.challenge).to.eql({});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -100,8 +100,8 @@ describe('POST /challenges/:challengeId/winner/:winnerId', () => {
|
|||||||
await sleep(0.5);
|
await sleep(0.5);
|
||||||
|
|
||||||
await expect(winningUser.sync()).to.eventually.have.deep.property('achievements.challenges').to.include(challenge.name);
|
await expect(winningUser.sync()).to.eventually.have.deep.property('achievements.challenges').to.include(challenge.name);
|
||||||
expect(winningUser.notifications.length).to.equal(1);
|
expect(winningUser.notifications.length).to.equal(2); // 2 because winningUser just joined the challenge, which now awards an achievement
|
||||||
expect(winningUser.notifications[0].type).to.equal('WON_CHALLENGE');
|
expect(winningUser.notifications[1].type).to.equal('WON_CHALLENGE');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('gives winner gems as reward', async () => {
|
it('gives winner gems as reward', async () => {
|
||||||
|
|||||||
@@ -84,6 +84,10 @@ describe('POST /chat/:chatId/flag', () => {
|
|||||||
type: 'party',
|
type: 'party',
|
||||||
privacy: 'private',
|
privacy: 'private',
|
||||||
});
|
});
|
||||||
|
await user.post(`/groups/${privateGroup._id}/invite`, {
|
||||||
|
uuids: [anotherUser._id],
|
||||||
|
});
|
||||||
|
await anotherUser.post(`/groups/${privateGroup._id}/join`);
|
||||||
let { message } = await user.post(`/groups/${privateGroup._id}/chat`, {message: TEST_MESSAGE});
|
let { message } = await user.post(`/groups/${privateGroup._id}/chat`, {message: TEST_MESSAGE});
|
||||||
|
|
||||||
let flagResult = await admin.post(`/groups/${privateGroup._id}/chat/${message.id}/flag`);
|
let flagResult = await admin.post(`/groups/${privateGroup._id}/chat/${message.id}/flag`);
|
||||||
@@ -91,7 +95,7 @@ describe('POST /chat/:chatId/flag', () => {
|
|||||||
expect(flagResult.flags[admin._id]).to.equal(true);
|
expect(flagResult.flags[admin._id]).to.equal(true);
|
||||||
expect(flagResult.flagCount).to.equal(5);
|
expect(flagResult.flagCount).to.equal(5);
|
||||||
|
|
||||||
let groupWithFlags = await user.get(`/groups/${privateGroup._id}`);
|
let groupWithFlags = await anotherUser.get(`/groups/${privateGroup._id}`);
|
||||||
let messageToCheck = find(groupWithFlags.chat, {id: message.id});
|
let messageToCheck = find(groupWithFlags.chat, {id: message.id});
|
||||||
|
|
||||||
expect(messageToCheck).to.not.exist;
|
expect(messageToCheck).to.not.exist;
|
||||||
@@ -125,4 +129,20 @@ describe('POST /chat/:chatId/flag', () => {
|
|||||||
message: t('messageGroupChatFlagAlreadyReported'),
|
message: t('messageGroupChatFlagAlreadyReported'),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('shows a hidden message to the original poster', async () => {
|
||||||
|
let { message } = await user.post(`/groups/${group._id}/chat`, {message: TEST_MESSAGE});
|
||||||
|
|
||||||
|
await admin.post(`/groups/${group._id}/chat/${message.id}/flag`);
|
||||||
|
|
||||||
|
let groupWithFlags = await user.get(`/groups/${group._id}`);
|
||||||
|
let messageToCheck = find(groupWithFlags.chat, {id: message.id});
|
||||||
|
|
||||||
|
expect(messageToCheck).to.exist;
|
||||||
|
|
||||||
|
let auGroupWithFlags = await anotherUser.get(`/groups/${group._id}`);
|
||||||
|
let auMessageToCheck = find(auGroupWithFlags.chat, {id: message.id});
|
||||||
|
|
||||||
|
expect(auMessageToCheck).to.not.exist;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,11 +4,17 @@ import {
|
|||||||
sleep,
|
sleep,
|
||||||
server,
|
server,
|
||||||
} from '../../../../helpers/api-v3-integration.helper';
|
} from '../../../../helpers/api-v3-integration.helper';
|
||||||
|
import {
|
||||||
|
SPAM_MESSAGE_LIMIT,
|
||||||
|
SPAM_MIN_EXEMPT_CONTRIB_LEVEL,
|
||||||
|
TAVERN_ID,
|
||||||
|
} from '../../../../../website/server/models/group';
|
||||||
import { v4 as generateUUID } from 'uuid';
|
import { v4 as generateUUID } from 'uuid';
|
||||||
|
|
||||||
describe('POST /chat', () => {
|
describe('POST /chat', () => {
|
||||||
let user, groupWithChat, userWithChatRevoked, member;
|
let user, groupWithChat, member, additionalMember;
|
||||||
let testMessage = 'Test Message';
|
let testMessage = 'Test Message';
|
||||||
|
let testBannedWordMessage = 'TEST_PLACEHOLDER_SWEAR_WORD_HERE';
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
let { group, groupLeader, members } = await createAndPopulateGroup({
|
let { group, groupLeader, members } = await createAndPopulateGroup({
|
||||||
@@ -22,8 +28,8 @@ describe('POST /chat', () => {
|
|||||||
|
|
||||||
user = groupLeader;
|
user = groupLeader;
|
||||||
groupWithChat = group;
|
groupWithChat = group;
|
||||||
userWithChatRevoked = await members[0].update({'flags.chatRevoked': true});
|
|
||||||
member = members[0];
|
member = members[0];
|
||||||
|
additionalMember = members[1];
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Returns an error when no message is provided', async () => {
|
it('Returns an error when no message is provided', async () => {
|
||||||
@@ -62,10 +68,101 @@ describe('POST /chat', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('returns an error when chat privileges are revoked when sending a message to a public guild', async () => {
|
it('returns an error when chat privileges are revoked when sending a message to a public guild', async () => {
|
||||||
|
let userWithChatRevoked = await member.update({'flags.chatRevoked': true});
|
||||||
await expect(userWithChatRevoked.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage})).to.eventually.be.rejected.and.eql({
|
await expect(userWithChatRevoked.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage})).to.eventually.be.rejected.and.eql({
|
||||||
code: 404,
|
code: 401,
|
||||||
error: 'NotFound',
|
error: 'NotAuthorized',
|
||||||
message: 'Your chat privileges have been revoked.',
|
message: t('chatPrivilegesRevoked'),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
context('banned word', () => {
|
||||||
|
it('returns an error when chat message contains a banned word in tavern', async () => {
|
||||||
|
await expect(user.post('/groups/habitrpg/chat', { message: testBannedWordMessage}))
|
||||||
|
.to.eventually.be.rejected.and.eql({
|
||||||
|
code: 400,
|
||||||
|
error: 'BadRequest',
|
||||||
|
message: t('bannedWordUsed'),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('errors when word is part of a phrase', async () => {
|
||||||
|
let wordInPhrase = `phrase ${testBannedWordMessage} end`;
|
||||||
|
await expect(user.post('/groups/habitrpg/chat', { message: wordInPhrase}))
|
||||||
|
.to.eventually.be.rejected.and.eql({
|
||||||
|
code: 400,
|
||||||
|
error: 'BadRequest',
|
||||||
|
message: t('bannedWordUsed'),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('errors when word is surrounded by non alphabet characters', async () => {
|
||||||
|
let wordInPhrase = `_!${testBannedWordMessage}@_`;
|
||||||
|
await expect(user.post('/groups/habitrpg/chat', { message: wordInPhrase}))
|
||||||
|
.to.eventually.be.rejected.and.eql({
|
||||||
|
code: 400,
|
||||||
|
error: 'BadRequest',
|
||||||
|
message: t('bannedWordUsed'),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not error when bad word is suffix of a word', async () => {
|
||||||
|
let wordAsSuffix = `prefix${testBannedWordMessage}`;
|
||||||
|
let message = await user.post('/groups/habitrpg/chat', { message: wordAsSuffix});
|
||||||
|
|
||||||
|
expect(message.message.id).to.exist;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not error when bad word is prefix of a word', async () => {
|
||||||
|
let wordAsPrefix = `${testBannedWordMessage}suffix`;
|
||||||
|
let message = await user.post('/groups/habitrpg/chat', { message: wordAsPrefix});
|
||||||
|
|
||||||
|
expect(message.message.id).to.exist;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not error when sending a chat message containing a banned word to a party', async () => {
|
||||||
|
let { group, members } = await createAndPopulateGroup({
|
||||||
|
groupDetails: {
|
||||||
|
name: 'Party',
|
||||||
|
type: 'party',
|
||||||
|
privacy: 'private',
|
||||||
|
},
|
||||||
|
members: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
let message = await members[0].post(`/groups/${group._id}/chat`, { message: testBannedWordMessage});
|
||||||
|
|
||||||
|
expect(message.message.id).to.exist;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not error when sending a chat message containing a banned word to a public guild', async () => {
|
||||||
|
let { group, members } = await createAndPopulateGroup({
|
||||||
|
groupDetails: {
|
||||||
|
name: 'public guild',
|
||||||
|
type: 'guild',
|
||||||
|
privacy: 'public',
|
||||||
|
},
|
||||||
|
members: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
let message = await members[0].post(`/groups/${group._id}/chat`, { message: testBannedWordMessage});
|
||||||
|
|
||||||
|
expect(message.message.id).to.exist;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not error when sending a chat message containing a banned word to a private guild', async () => {
|
||||||
|
let { group, members } = await createAndPopulateGroup({
|
||||||
|
groupDetails: {
|
||||||
|
name: 'private guild',
|
||||||
|
type: 'guild',
|
||||||
|
privacy: 'private',
|
||||||
|
},
|
||||||
|
members: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
let message = await members[0].post(`/groups/${group._id}/chat`, { message: testBannedWordMessage});
|
||||||
|
|
||||||
|
expect(message.message.id).to.exist;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -173,4 +270,30 @@ describe('POST /chat', () => {
|
|||||||
expect(message.message.id).to.exist;
|
expect(message.message.id).to.exist;
|
||||||
expect(memberWithNotification.newMessages[`${group._id}`]).to.exist;
|
expect(memberWithNotification.newMessages[`${group._id}`]).to.exist;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
context('Spam prevention', () => {
|
||||||
|
it('Returns an error when the user has been posting too many messages', async () => {
|
||||||
|
// Post as many messages are needed to reach the spam limit
|
||||||
|
for (let i = 0; i < SPAM_MESSAGE_LIMIT; i++) {
|
||||||
|
let result = await additionalMember.post(`/groups/${TAVERN_ID}/chat`, { message: testMessage }); // eslint-disable-line no-await-in-loop
|
||||||
|
expect(result.message.id).to.exist;
|
||||||
|
}
|
||||||
|
|
||||||
|
await expect(additionalMember.post(`/groups/${TAVERN_ID}/chat`, { message: testMessage })).to.eventually.be.rejected.and.eql({
|
||||||
|
code: 401,
|
||||||
|
error: 'NotAuthorized',
|
||||||
|
message: t('messageGroupChatSpam'),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('contributor should not receive spam alert', async () => {
|
||||||
|
let userSocialite = await member.update({'contributor.level': SPAM_MIN_EXEMPT_CONTRIB_LEVEL, 'flags.chatRevoked': false});
|
||||||
|
|
||||||
|
// Post 1 more message than the spam limit to ensure they do not reach the limit
|
||||||
|
for (let i = 0; i < SPAM_MESSAGE_LIMIT + 1; i++) {
|
||||||
|
let result = await userSocialite.post(`/groups/${TAVERN_ID}/chat`, { message: testMessage }); // eslint-disable-line no-await-in-loop
|
||||||
|
expect(result.message.id).to.exist;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
TAVERN_ID,
|
TAVERN_ID,
|
||||||
} from '../../../../../website/server/models/group';
|
} from '../../../../../website/server/models/group';
|
||||||
|
import apiMessages from '../../../../../website/server/libs/apiMessages';
|
||||||
|
|
||||||
describe('GET /groups', () => {
|
describe('GET /groups', () => {
|
||||||
let user;
|
let user;
|
||||||
@@ -14,6 +15,7 @@ describe('GET /groups', () => {
|
|||||||
const NUMBER_OF_PUBLIC_GUILDS_USER_IS_MEMBER = 1;
|
const NUMBER_OF_PUBLIC_GUILDS_USER_IS_MEMBER = 1;
|
||||||
const NUMBER_OF_USERS_PRIVATE_GUILDS = 1;
|
const NUMBER_OF_USERS_PRIVATE_GUILDS = 1;
|
||||||
const NUMBER_OF_GROUPS_USER_CAN_VIEW = 5;
|
const NUMBER_OF_GROUPS_USER_CAN_VIEW = 5;
|
||||||
|
const GUILD_PER_PAGE = 30;
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
await resetHabiticaDB();
|
await resetHabiticaDB();
|
||||||
@@ -98,6 +100,60 @@ describe('GET /groups', () => {
|
|||||||
.to.eventually.have.a.lengthOf(NUMBER_OF_PUBLIC_GUILDS);
|
.to.eventually.have.a.lengthOf(NUMBER_OF_PUBLIC_GUILDS);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('public guilds pagination', () => {
|
||||||
|
it('req.query.paginate must be a boolean string', async () => {
|
||||||
|
await expect(user.get('/groups?paginate=aString&type=publicGuilds'))
|
||||||
|
.to.eventually.be.rejected.and.eql({
|
||||||
|
code: 400,
|
||||||
|
error: 'BadRequest',
|
||||||
|
message: 'Invalid request parameters.',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('req.query.paginate can only be true when req.query.type includes publicGuilds', async () => {
|
||||||
|
await expect(user.get('/groups?paginate=true&type=notPublicGuilds'))
|
||||||
|
.to.eventually.be.rejected.and.eql({
|
||||||
|
code: 400,
|
||||||
|
error: 'BadRequest',
|
||||||
|
message: apiMessages('guildsOnlyPaginate'),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('req.query.page can\'t be negative', async () => {
|
||||||
|
await expect(user.get('/groups?paginate=true&page=-1&type=publicGuilds'))
|
||||||
|
.to.eventually.be.rejected.and.eql({
|
||||||
|
code: 400,
|
||||||
|
error: 'BadRequest',
|
||||||
|
message: 'Invalid request parameters.',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns 30 guilds per page ordered by number of members', async () => {
|
||||||
|
await user.update({balance: 9000});
|
||||||
|
let groups = await Promise.all(_.times(60, (i) => {
|
||||||
|
return generateGroup(user, {
|
||||||
|
name: `public guild ${i} - is member`,
|
||||||
|
type: 'guild',
|
||||||
|
privacy: 'public',
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
// update group number 32 and not the first to make sure sorting works
|
||||||
|
await groups[32].update({name: 'guild with most members', memberCount: 199});
|
||||||
|
await groups[33].update({name: 'guild with less members', memberCount: -100});
|
||||||
|
|
||||||
|
let page0 = await expect(user.get('/groups?type=publicGuilds&paginate=true'))
|
||||||
|
.to.eventually.have.a.lengthOf(GUILD_PER_PAGE);
|
||||||
|
expect(page0[0].name).to.equal('guild with most members');
|
||||||
|
|
||||||
|
await expect(user.get('/groups?type=publicGuilds&paginate=true&page=1'))
|
||||||
|
.to.eventually.have.a.lengthOf(GUILD_PER_PAGE);
|
||||||
|
let page2 = await expect(user.get('/groups?type=publicGuilds&paginate=true&page=2'))
|
||||||
|
.to.eventually.have.a.lengthOf(1 + 2); // 1 created now, 2 by other tests
|
||||||
|
expect(page2[2].name).to.equal('guild with less members');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('returns all the user\'s guilds when guilds passed in as query', async () => {
|
it('returns all the user\'s guilds when guilds passed in as query', async () => {
|
||||||
await expect(user.get('/groups?type=guilds'))
|
await expect(user.get('/groups?type=guilds'))
|
||||||
.to.eventually.have.a.lengthOf(NUMBER_OF_PUBLIC_GUILDS_USER_IS_MEMBER + NUMBER_OF_USERS_PRIVATE_GUILDS);
|
.to.eventually.have.a.lengthOf(NUMBER_OF_PUBLIC_GUILDS_USER_IS_MEMBER + NUMBER_OF_USERS_PRIVATE_GUILDS);
|
||||||
|
|||||||
@@ -63,15 +63,17 @@ describe('GET /groups/:groupId/invites', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('returns only first 30 invites', async () => {
|
it('returns only first 30 invites', async () => {
|
||||||
let group = await generateGroup(user, {type: 'party', name: generateUUID()});
|
let leader = await generateUser({balance: 4});
|
||||||
|
let group = await generateGroup(leader, {type: 'guild', privacy: 'public', name: generateUUID()});
|
||||||
|
|
||||||
let invitesToGenerate = [];
|
let invitesToGenerate = [];
|
||||||
for (let i = 0; i < 31; i++) {
|
for (let i = 0; i < 31; i++) {
|
||||||
invitesToGenerate.push(generateUser());
|
invitesToGenerate.push(generateUser());
|
||||||
}
|
}
|
||||||
let generatedInvites = await Promise.all(invitesToGenerate);
|
let generatedInvites = await Promise.all(invitesToGenerate);
|
||||||
await user.post(`/groups/${group._id}/invite`, {uuids: generatedInvites.map(invite => invite._id)});
|
await leader.post(`/groups/${group._id}/invite`, {uuids: generatedInvites.map(invite => invite._id)});
|
||||||
|
|
||||||
let res = await user.get('/groups/party/invites');
|
let res = await leader.get(`/groups/${group._id}/invites`);
|
||||||
expect(res.length).to.equal(30);
|
expect(res.length).to.equal(30);
|
||||||
res.forEach(member => {
|
res.forEach(member => {
|
||||||
expect(member).to.have.all.keys(['_id', 'id', 'profile']);
|
expect(member).to.have.all.keys(['_id', 'id', 'profile']);
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ describe('GET /groups/:groupId/members', () => {
|
|||||||
expect(Object.keys(memberRes.auth)).to.eql(['timestamps']);
|
expect(Object.keys(memberRes.auth)).to.eql(['timestamps']);
|
||||||
expect(Object.keys(memberRes.preferences).sort()).to.eql([
|
expect(Object.keys(memberRes.preferences).sort()).to.eql([
|
||||||
'size', 'hair', 'skin', 'shirt',
|
'size', 'hair', 'skin', 'shirt',
|
||||||
'chair', 'costume', 'sleep', 'background',
|
'chair', 'costume', 'sleep', 'background', 'tasks',
|
||||||
].sort());
|
].sort());
|
||||||
|
|
||||||
expect(memberRes.stats.maxMP).to.exist;
|
expect(memberRes.stats.maxMP).to.exist;
|
||||||
|
|||||||
@@ -0,0 +1,93 @@
|
|||||||
|
import {
|
||||||
|
createAndPopulateGroup,
|
||||||
|
translate as t,
|
||||||
|
} from '../../../../helpers/api-v3-integration.helper';
|
||||||
|
import { find } from 'lodash';
|
||||||
|
|
||||||
|
describe('POST /group/:groupId/remove-manager', () => {
|
||||||
|
let leader, nonLeader, groupToUpdate;
|
||||||
|
let groupName = 'Test Public Guild';
|
||||||
|
let groupType = 'guild';
|
||||||
|
let nonManager;
|
||||||
|
|
||||||
|
function findAssignedTask (memberTask) {
|
||||||
|
return memberTask.group.id === groupToUpdate._id;
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
let { group, groupLeader, members } = await createAndPopulateGroup({
|
||||||
|
groupDetails: {
|
||||||
|
name: groupName,
|
||||||
|
type: groupType,
|
||||||
|
privacy: 'public',
|
||||||
|
},
|
||||||
|
members: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
groupToUpdate = group;
|
||||||
|
leader = groupLeader;
|
||||||
|
nonLeader = members[0];
|
||||||
|
nonManager = members[0];
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns an error when a non group leader tries to add member', async () => {
|
||||||
|
await expect(nonLeader.post(`/groups/${groupToUpdate._id}/remove-manager`, {
|
||||||
|
managerId: nonLeader._id,
|
||||||
|
})).to.eventually.be.rejected.and.eql({
|
||||||
|
code: 401,
|
||||||
|
error: 'NotAuthorized',
|
||||||
|
message: t('messageGroupOnlyLeaderCanUpdate'),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns an error when manager does not exist', async () => {
|
||||||
|
await expect(leader.post(`/groups/${groupToUpdate._id}/remove-manager`, {
|
||||||
|
managerId: nonManager._id,
|
||||||
|
})).to.eventually.be.rejected.and.eql({
|
||||||
|
code: 401,
|
||||||
|
error: 'NotAuthorized',
|
||||||
|
message: t('userIsNotManager'),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('allows a leader to remove managers', async () => {
|
||||||
|
await leader.post(`/groups/${groupToUpdate._id}/add-manager`, {
|
||||||
|
managerId: nonLeader._id,
|
||||||
|
});
|
||||||
|
|
||||||
|
let updatedGroup = await leader.post(`/groups/${groupToUpdate._id}/remove-manager`, {
|
||||||
|
managerId: nonLeader._id,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(updatedGroup.managers[nonLeader._id]).to.not.exist;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('removes group approval notifications from a manager that is removed', async () => {
|
||||||
|
await leader.post(`/groups/${groupToUpdate._id}/add-manager`, {
|
||||||
|
managerId: nonLeader._id,
|
||||||
|
});
|
||||||
|
let task = await leader.post(`/tasks/group/${groupToUpdate._id}`, {
|
||||||
|
text: 'test todo',
|
||||||
|
type: 'todo',
|
||||||
|
requiresApproval: true,
|
||||||
|
});
|
||||||
|
await nonLeader.post(`/tasks/${task._id}/assign/${leader._id}`);
|
||||||
|
let memberTasks = await leader.get('/tasks/user');
|
||||||
|
let syncedTask = find(memberTasks, findAssignedTask);
|
||||||
|
await expect(leader.post(`/tasks/${syncedTask._id}/score/up`))
|
||||||
|
.to.eventually.be.rejected.and.to.eql({
|
||||||
|
code: 401,
|
||||||
|
error: 'NotAuthorized',
|
||||||
|
message: t('taskApprovalHasBeenRequested'),
|
||||||
|
});
|
||||||
|
|
||||||
|
let updatedGroup = await leader.post(`/groups/${groupToUpdate._id}/remove-manager`, {
|
||||||
|
managerId: nonLeader._id,
|
||||||
|
});
|
||||||
|
|
||||||
|
await nonLeader.sync();
|
||||||
|
|
||||||
|
expect(nonLeader.notifications.length).to.equal(0);
|
||||||
|
expect(updatedGroup.managers[nonLeader._id]).to.not.exist;
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -74,6 +74,18 @@ describe('POST /group', () => {
|
|||||||
expect(updatedUser.guilds).to.include(guild._id);
|
expect(updatedUser.guilds).to.include(guild._id);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('awards the Joined Guild achievement', async () => {
|
||||||
|
await user.post('/groups', {
|
||||||
|
name: 'some guild',
|
||||||
|
type: 'guild',
|
||||||
|
privacy: 'public',
|
||||||
|
});
|
||||||
|
|
||||||
|
let updatedUser = await user.get('/user');
|
||||||
|
|
||||||
|
expect(updatedUser.achievements.joinedGuild).to.eql(true);
|
||||||
|
});
|
||||||
|
|
||||||
context('public guild', () => {
|
context('public guild', () => {
|
||||||
it('creates a group', async () => {
|
it('creates a group', async () => {
|
||||||
let groupName = 'Test Public Guild';
|
let groupName = 'Test Public Guild';
|
||||||
|
|||||||
@@ -68,6 +68,12 @@ describe('POST /group/:groupId/join', () => {
|
|||||||
|
|
||||||
await expect(joiningUser.get(`/groups/${publicGuild._id}`)).to.eventually.have.property('memberCount', oldMemberCount + 1);
|
await expect(joiningUser.get(`/groups/${publicGuild._id}`)).to.eventually.have.property('memberCount', oldMemberCount + 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('awards Joined Guild achievement', async () => {
|
||||||
|
await joiningUser.post(`/groups/${publicGuild._id}/join`);
|
||||||
|
|
||||||
|
await expect(joiningUser.get('/user')).to.eventually.have.deep.property('achievements.joinedGuild', true);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
context('Joining a private guild', () => {
|
context('Joining a private guild', () => {
|
||||||
@@ -147,8 +153,14 @@ describe('POST /group/:groupId/join', () => {
|
|||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(inviter.notifications[0].type).to.eql('GROUP_INVITE_ACCEPTED');
|
expect(inviter.notifications[1].type).to.eql('GROUP_INVITE_ACCEPTED');
|
||||||
expect(inviter.notifications[0].data).to.eql(expectedData);
|
expect(inviter.notifications[1].data).to.eql(expectedData);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('awards Joined Guild achievement', async () => {
|
||||||
|
await invitedUser.post(`/groups/${guild._id}/join`);
|
||||||
|
|
||||||
|
await expect(invitedUser.get('/user')).to.eventually.have.deep.property('achievements.joinedGuild', true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ describe('POST /groups/:groupId/leave', () => {
|
|||||||
expect(leader.newMessages[groupToLeave._id]).to.be.empty;
|
expect(leader.newMessages[groupToLeave._id]).to.be.empty;
|
||||||
});
|
});
|
||||||
|
|
||||||
context('With challenges', () => {
|
context('with challenges', () => {
|
||||||
let challenge;
|
let challenge;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
@@ -106,10 +106,25 @@ describe('POST /groups/:groupId/leave', () => {
|
|||||||
|
|
||||||
let userWithChallengeTasks = await leader.get('/user');
|
let userWithChallengeTasks = await leader.get('/user');
|
||||||
|
|
||||||
expect(userWithChallengeTasks.challenges).to.not.include(challenge._id);
|
|
||||||
// @TODO find elegant way to assert against the task existing
|
// @TODO find elegant way to assert against the task existing
|
||||||
expect(userWithChallengeTasks.tasksOrder.habits).to.not.be.empty;
|
expect(userWithChallengeTasks.tasksOrder.habits).to.not.be.empty;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('keeps the user in the challenge when the keepChallenges parameter is set to remain-in-challenges', async () => {
|
||||||
|
await leader.post(`/groups/${groupToLeave._id}/leave`, {keepChallenges: 'remain-in-challenges'});
|
||||||
|
|
||||||
|
let userWithChallengeTasks = await leader.get('/user');
|
||||||
|
|
||||||
|
expect(userWithChallengeTasks.challenges).to.include(challenge._id);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('drops the user in the challenge when the keepChallenges parameter isn\'t set', async () => {
|
||||||
|
await leader.post(`/groups/${groupToLeave._id}/leave`);
|
||||||
|
|
||||||
|
let userWithChallengeTasks = await leader.get('/user');
|
||||||
|
|
||||||
|
expect(userWithChallengeTasks.challenges).to.not.include(challenge._id);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('prevents quest leader from leaving a groupToLeave');
|
it('prevents quest leader from leaving a groupToLeave');
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ describe('POST /groups/:groupId/removeMember/:memberId', () => {
|
|||||||
let guild;
|
let guild;
|
||||||
let member;
|
let member;
|
||||||
let member2;
|
let member2;
|
||||||
|
let adminUser;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
let { group, groupLeader, invitees, members } = await createAndPopulateGroup({
|
let { group, groupLeader, invitees, members } = await createAndPopulateGroup({
|
||||||
@@ -28,6 +29,7 @@ describe('POST /groups/:groupId/removeMember/:memberId', () => {
|
|||||||
invitedUser = invitees[0];
|
invitedUser = invitees[0];
|
||||||
member = members[0];
|
member = members[0];
|
||||||
member2 = members[1];
|
member2 = members[1];
|
||||||
|
adminUser = await generateUser({ 'contributor.admin': true });
|
||||||
});
|
});
|
||||||
|
|
||||||
context('All Groups', () => {
|
context('All Groups', () => {
|
||||||
@@ -42,7 +44,7 @@ describe('POST /groups/:groupId/removeMember/:memberId', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns an error when user is a non-leader member of a group', async () => {
|
it('returns an error when user is a non-leader member of a group and not an admin', async () => {
|
||||||
expect(member2.post(`/groups/${guild._id}/removeMember/${member._id}`))
|
expect(member2.post(`/groups/${guild._id}/removeMember/${member._id}`))
|
||||||
.to.eventually.be.rejected.and.eql({
|
.to.eventually.be.rejected.and.eql({
|
||||||
code: 401,
|
code: 401,
|
||||||
@@ -87,7 +89,30 @@ describe('POST /groups/:groupId/removeMember/:memberId', () => {
|
|||||||
|
|
||||||
let invitedUserWithoutInvite = await invitedUser.get('/user');
|
let invitedUserWithoutInvite = await invitedUser.get('/user');
|
||||||
|
|
||||||
expect(_.findIndex(invitedUserWithoutInvite.invitations.guilds, {id: guild._id})).eql(-1);
|
expect(_.findIndex(invitedUserWithoutInvite.invitations.guilds, { id: guild._id })).eql(-1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('allows an admin to remove other members', async () => {
|
||||||
|
await adminUser.post(`/groups/${guild._id}/removeMember/${member._id}`);
|
||||||
|
let memberRemoved = await member.get('/user');
|
||||||
|
|
||||||
|
expect(memberRemoved.guilds.indexOf(guild._id)).eql(-1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('allows an admin to remove other invites', async () => {
|
||||||
|
await adminUser.post(`/groups/${guild._id}/removeMember/${invitedUser._id}`);
|
||||||
|
|
||||||
|
let invitedUserWithoutInvite = await invitedUser.get('/user');
|
||||||
|
|
||||||
|
expect(_.findIndex(invitedUserWithoutInvite.invitations.guilds, { id: guild._id })).eql(-1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not allow an admin to remove a leader', async () => {
|
||||||
|
expect(adminUser.post(`/groups/${guild._id}/removeMember/${leader._id}`))
|
||||||
|
.to.eventually.be.rejected.and.eql({
|
||||||
|
code: 401,
|
||||||
|
text: t('cannotRemoveCurrentLeader'),
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('sends email to user with rescinded invite', async () => {
|
it('sends email to user with rescinded invite', async () => {
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
import {
|
import {
|
||||||
generateUser,
|
generateUser,
|
||||||
|
generateGroup,
|
||||||
translate as t,
|
translate as t,
|
||||||
} from '../../../../helpers/api-v3-integration.helper';
|
} from '../../../../helpers/api-v3-integration.helper';
|
||||||
import { v4 as generateUUID } from 'uuid';
|
import { v4 as generateUUID } from 'uuid';
|
||||||
|
import nconf from 'nconf';
|
||||||
|
|
||||||
const INVITES_LIMIT = 100;
|
const INVITES_LIMIT = 100;
|
||||||
|
const PARTY_LIMIT_MEMBERS = 30;
|
||||||
|
const MAX_EMAIL_INVITES_BY_USER = 200;
|
||||||
|
|
||||||
describe('Post /groups/:groupId/invite', () => {
|
describe('Post /groups/:groupId/invite', () => {
|
||||||
let inviter;
|
let inviter;
|
||||||
@@ -12,7 +16,7 @@ describe('Post /groups/:groupId/invite', () => {
|
|||||||
let groupName = 'Test Public Guild';
|
let groupName = 'Test Public Guild';
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
inviter = await generateUser({balance: 1});
|
inviter = await generateUser({balance: 4});
|
||||||
group = await inviter.post('/groups', {
|
group = await inviter.post('/groups', {
|
||||||
name: groupName,
|
name: groupName,
|
||||||
type: 'guild',
|
type: 'guild',
|
||||||
@@ -203,13 +207,37 @@ describe('Post /groups/:groupId/invite', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('returns an error when a user has sent the max number of email invites', async () => {
|
||||||
|
let inviterWithMax = await generateUser({
|
||||||
|
invitesSent: MAX_EMAIL_INVITES_BY_USER,
|
||||||
|
balance: 4,
|
||||||
|
});
|
||||||
|
let tmpGroup = await inviterWithMax.post('/groups', {
|
||||||
|
name: groupName,
|
||||||
|
type: 'guild',
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(inviterWithMax.post(`/groups/${tmpGroup._id}/invite`, {
|
||||||
|
emails: [testInvite],
|
||||||
|
inviter: 'inviter name',
|
||||||
|
}))
|
||||||
|
.to.eventually.be.rejected.and.eql({
|
||||||
|
code: 401,
|
||||||
|
error: 'NotAuthorized',
|
||||||
|
message: t('inviteLimitReached', {techAssistanceEmail: nconf.get('EMAILS:TECH_ASSISTANCE_EMAIL')}),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('invites a user to a group by email', async () => {
|
it('invites a user to a group by email', async () => {
|
||||||
let res = await inviter.post(`/groups/${group._id}/invite`, {
|
let res = await inviter.post(`/groups/${group._id}/invite`, {
|
||||||
emails: [testInvite],
|
emails: [testInvite],
|
||||||
inviter: 'inviter name',
|
inviter: 'inviter name',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let updatedUser = await inviter.sync();
|
||||||
|
|
||||||
expect(res).to.exist;
|
expect(res).to.exist;
|
||||||
|
expect(updatedUser.invitesSent).to.eql(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('invites multiple users to a group by email', async () => {
|
it('invites multiple users to a group by email', async () => {
|
||||||
@@ -217,7 +245,10 @@ describe('Post /groups/:groupId/invite', () => {
|
|||||||
emails: [testInvite, {name: 'test2', email: 'test2@habitica.com'}],
|
emails: [testInvite, {name: 'test2', email: 'test2@habitica.com'}],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let updatedUser = await inviter.sync();
|
||||||
|
|
||||||
expect(res).to.exist;
|
expect(res).to.exist;
|
||||||
|
expect(updatedUser.invitesSent).to.eql(2);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -265,6 +296,25 @@ describe('Post /groups/:groupId/invite', () => {
|
|||||||
expect(invitedUser.invitations.guilds[0].id).to.equal(group._id);
|
expect(invitedUser.invitations.guilds[0].id).to.equal(group._id);
|
||||||
expect(invite).to.exist;
|
expect(invite).to.exist;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('invites marks invite with cancelled plan', async () => {
|
||||||
|
let cancelledPlanGroup = await generateGroup(inviter, {
|
||||||
|
type: 'guild',
|
||||||
|
name: generateUUID(),
|
||||||
|
});
|
||||||
|
await cancelledPlanGroup.createCancelledSubscription();
|
||||||
|
|
||||||
|
let newUser = await generateUser();
|
||||||
|
let invite = await inviter.post(`/groups/${cancelledPlanGroup._id}/invite`, {
|
||||||
|
uuids: [newUser._id],
|
||||||
|
emails: [{name: 'test', email: 'test@habitica.com'}],
|
||||||
|
});
|
||||||
|
let invitedUser = await newUser.get('/user');
|
||||||
|
|
||||||
|
expect(invitedUser.invitations.guilds[0].id).to.equal(cancelledPlanGroup._id);
|
||||||
|
expect(invitedUser.invitations.guilds[0].cancelledPlan).to.be.true;
|
||||||
|
expect(invite).to.exist;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('guild invites', () => {
|
describe('guild invites', () => {
|
||||||
@@ -301,6 +351,19 @@ describe('Post /groups/:groupId/invite', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('allows 30+ members in a guild', async () => {
|
||||||
|
let invitesToGenerate = [];
|
||||||
|
// Generate 30 users to invite (30 + leader = 31 members)
|
||||||
|
for (let i = 0; i < PARTY_LIMIT_MEMBERS; i++) {
|
||||||
|
invitesToGenerate.push(generateUser());
|
||||||
|
}
|
||||||
|
let generatedInvites = await Promise.all(invitesToGenerate);
|
||||||
|
// Invite users
|
||||||
|
expect(await inviter.post(`/groups/${group._id}/invite`, {
|
||||||
|
uuids: generatedInvites.map(invite => invite._id),
|
||||||
|
})).to.be.an('array');
|
||||||
|
});
|
||||||
|
|
||||||
// @TODO: Add this after we are able to mock the group plan route
|
// @TODO: Add this after we are able to mock the group plan route
|
||||||
xit('returns an error when a non-leader invites to a group plan', async () => {
|
xit('returns an error when a non-leader invites to a group plan', async () => {
|
||||||
let userToInvite = await generateUser();
|
let userToInvite = await generateUser();
|
||||||
@@ -390,5 +453,36 @@ describe('Post /groups/:groupId/invite', () => {
|
|||||||
});
|
});
|
||||||
expect((await userToInvite.get('/user')).invitations.party.id).to.equal(party._id);
|
expect((await userToInvite.get('/user')).invitations.party.id).to.equal(party._id);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('allows 30 members in a party', async () => {
|
||||||
|
let invitesToGenerate = [];
|
||||||
|
// Generate 29 users to invite (29 + leader = 30 members)
|
||||||
|
for (let i = 0; i < PARTY_LIMIT_MEMBERS - 1; i++) {
|
||||||
|
invitesToGenerate.push(generateUser());
|
||||||
|
}
|
||||||
|
let generatedInvites = await Promise.all(invitesToGenerate);
|
||||||
|
// Invite users
|
||||||
|
expect(await inviter.post(`/groups/${party._id}/invite`, {
|
||||||
|
uuids: generatedInvites.map(invite => invite._id),
|
||||||
|
})).to.be.an('array');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not allow 30+ members in a party', async () => {
|
||||||
|
let invitesToGenerate = [];
|
||||||
|
// Generate 30 users to invite (30 + leader = 31 members)
|
||||||
|
for (let i = 0; i < PARTY_LIMIT_MEMBERS; i++) {
|
||||||
|
invitesToGenerate.push(generateUser());
|
||||||
|
}
|
||||||
|
let generatedInvites = await Promise.all(invitesToGenerate);
|
||||||
|
// Invite users
|
||||||
|
await expect(inviter.post(`/groups/${party._id}/invite`, {
|
||||||
|
uuids: generatedInvites.map(invite => invite._id),
|
||||||
|
}))
|
||||||
|
.to.eventually.be.rejected.and.eql({
|
||||||
|
code: 400,
|
||||||
|
error: 'BadRequest',
|
||||||
|
message: t('partyExceedsMembersLimit', {maxMembersParty: PARTY_LIMIT_MEMBERS}),
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
85
test/api/v3/integration/groups/POST-groups_manager.test.js
Normal file
85
test/api/v3/integration/groups/POST-groups_manager.test.js
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
import {
|
||||||
|
generateUser,
|
||||||
|
createAndPopulateGroup,
|
||||||
|
translate as t,
|
||||||
|
} from '../../../../helpers/api-v3-integration.helper';
|
||||||
|
|
||||||
|
describe('POST /group/:groupId/add-manager', () => {
|
||||||
|
let leader, nonLeader, groupToUpdate;
|
||||||
|
let groupName = 'Test Public Guild';
|
||||||
|
let groupType = 'guild';
|
||||||
|
let nonMember;
|
||||||
|
|
||||||
|
context('Guilds', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
let { group, groupLeader, members } = await createAndPopulateGroup({
|
||||||
|
groupDetails: {
|
||||||
|
name: groupName,
|
||||||
|
type: groupType,
|
||||||
|
privacy: 'public',
|
||||||
|
},
|
||||||
|
members: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
groupToUpdate = group;
|
||||||
|
leader = groupLeader;
|
||||||
|
nonLeader = members[0];
|
||||||
|
nonMember = await generateUser();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns an error when a non group leader tries to add member', async () => {
|
||||||
|
await expect(nonLeader.post(`/groups/${groupToUpdate._id}/add-manager`, {
|
||||||
|
managerId: nonLeader._id,
|
||||||
|
})).to.eventually.be.rejected.and.eql({
|
||||||
|
code: 401,
|
||||||
|
error: 'NotAuthorized',
|
||||||
|
message: t('messageGroupOnlyLeaderCanUpdate'),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns an error when trying to promote a non member', async () => {
|
||||||
|
await expect(leader.post(`/groups/${groupToUpdate._id}/add-manager`, {
|
||||||
|
managerId: nonMember._id,
|
||||||
|
})).to.eventually.be.rejected.and.eql({
|
||||||
|
code: 401,
|
||||||
|
error: 'NotAuthorized',
|
||||||
|
message: t('userMustBeMember'),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('allows a leader to add managers', async () => {
|
||||||
|
let updatedGroup = await leader.post(`/groups/${groupToUpdate._id}/add-manager`, {
|
||||||
|
managerId: nonLeader._id,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(updatedGroup.managers[nonLeader._id]).to.be.true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
context('Party', () => {
|
||||||
|
let party, partyLeader, partyNonLeader;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
let { group, groupLeader, members } = await createAndPopulateGroup({
|
||||||
|
groupDetails: {
|
||||||
|
name: groupName,
|
||||||
|
type: 'party',
|
||||||
|
privacy: 'private',
|
||||||
|
},
|
||||||
|
members: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
party = group;
|
||||||
|
partyLeader = groupLeader;
|
||||||
|
partyNonLeader = members[0];
|
||||||
|
});
|
||||||
|
|
||||||
|
it('allows leader of party to add managers', async () => {
|
||||||
|
let updatedGroup = await partyLeader.post(`/groups/${party._id}/add-manager`, {
|
||||||
|
managerId: partyNonLeader._id,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(updatedGroup.managers[partyNonLeader._id]).to.be.true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
import {
|
import {
|
||||||
createAndPopulateGroup,
|
createAndPopulateGroup,
|
||||||
|
generateUser,
|
||||||
translate as t,
|
translate as t,
|
||||||
} from '../../../../helpers/api-v3-integration.helper';
|
} from '../../../../helpers/api-v3-integration.helper';
|
||||||
|
|
||||||
describe('PUT /group', () => {
|
describe('PUT /group', () => {
|
||||||
let leader, nonLeader, groupToUpdate;
|
let leader, nonLeader, groupToUpdate, adminUser;
|
||||||
let groupName = 'Test Public Guild';
|
let groupName = 'Test Public Guild';
|
||||||
let groupType = 'guild';
|
let groupType = 'guild';
|
||||||
let groupUpdatedName = 'Test Public Guild Updated';
|
let groupUpdatedName = 'Test Public Guild Updated';
|
||||||
@@ -18,13 +19,13 @@ describe('PUT /group', () => {
|
|||||||
},
|
},
|
||||||
members: 1,
|
members: 1,
|
||||||
});
|
});
|
||||||
|
adminUser = await generateUser({ 'contributor.admin': true });
|
||||||
groupToUpdate = group;
|
groupToUpdate = group;
|
||||||
leader = groupLeader;
|
leader = groupLeader;
|
||||||
nonLeader = members[0];
|
nonLeader = members[0];
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns an error when a non group leader tries to update', async () => {
|
it('returns an error when a user that is not an admin or group leader tries to update', async () => {
|
||||||
await expect(nonLeader.put(`/groups/${groupToUpdate._id}`, {
|
await expect(nonLeader.put(`/groups/${groupToUpdate._id}`, {
|
||||||
name: groupUpdatedName,
|
name: groupUpdatedName,
|
||||||
})).to.eventually.be.rejected.and.eql({
|
})).to.eventually.be.rejected.and.eql({
|
||||||
@@ -43,4 +44,24 @@ describe('PUT /group', () => {
|
|||||||
expect(updatedGroup.leader.profile.name).to.eql(leader.profile.name);
|
expect(updatedGroup.leader.profile.name).to.eql(leader.profile.name);
|
||||||
expect(updatedGroup.name).to.equal(groupUpdatedName);
|
expect(updatedGroup.name).to.equal(groupUpdatedName);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('allows an admin to update a guild', async () => {
|
||||||
|
let updatedGroup = await adminUser.put(`/groups/${groupToUpdate._id}`, {
|
||||||
|
name: groupUpdatedName,
|
||||||
|
});
|
||||||
|
expect(updatedGroup.leader._id).to.eql(leader._id);
|
||||||
|
expect(updatedGroup.leader.profile.name).to.eql(leader.profile.name);
|
||||||
|
expect(updatedGroup.name).to.equal(groupUpdatedName);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('allows a leader to change leaders', async () => {
|
||||||
|
let updatedGroup = await leader.put(`/groups/${groupToUpdate._id}`, {
|
||||||
|
name: groupUpdatedName,
|
||||||
|
leader: nonLeader._id,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(updatedGroup.leader._id).to.eql(nonLeader._id);
|
||||||
|
expect(updatedGroup.leader.profile.name).to.eql(nonLeader.profile.name);
|
||||||
|
expect(updatedGroup.name).to.equal(groupUpdatedName);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ describe('GET /members/:memberId', () => {
|
|||||||
expect(Object.keys(memberRes.auth)).to.eql(['timestamps']);
|
expect(Object.keys(memberRes.auth)).to.eql(['timestamps']);
|
||||||
expect(Object.keys(memberRes.preferences).sort()).to.eql([
|
expect(Object.keys(memberRes.preferences).sort()).to.eql([
|
||||||
'size', 'hair', 'skin', 'shirt',
|
'size', 'hair', 'skin', 'shirt',
|
||||||
'chair', 'costume', 'sleep', 'background',
|
'chair', 'costume', 'sleep', 'background', 'tasks',
|
||||||
].sort());
|
].sort());
|
||||||
|
|
||||||
expect(memberRes.stats.maxMP).to.exist;
|
expect(memberRes.stats.maxMP).to.exist;
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
import {
|
||||||
|
generateUser,
|
||||||
|
translate as t,
|
||||||
|
} from '../../../../helpers/api-v3-integration.helper';
|
||||||
|
import { v4 as generateUUID } from 'uuid';
|
||||||
|
|
||||||
|
describe('GET /members/:toUserId/objections/:interaction', () => {
|
||||||
|
let user;
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
user = await generateUser();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('validates req.params.memberId', async () => {
|
||||||
|
await expect(
|
||||||
|
user.get('/members/invalidUUID/objections/send-private-message')
|
||||||
|
).to.eventually.be.rejected.and.eql({
|
||||||
|
code: 400,
|
||||||
|
error: 'BadRequest',
|
||||||
|
message: t('invalidReqParams'),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles non-existing members', async () => {
|
||||||
|
let dummyId = generateUUID();
|
||||||
|
await expect(
|
||||||
|
user.get(`/members/${dummyId}/objections/send-private-message`)
|
||||||
|
).to.eventually.be.rejected.and.eql({
|
||||||
|
code: 404,
|
||||||
|
error: 'NotFound',
|
||||||
|
message: t('userWithIDNotFound', {userId: dummyId}),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles non-existing interactions', async () => {
|
||||||
|
let receiver = await generateUser();
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
user.get(`/members/${receiver._id}/objections/hug-a-whole-forest-of-trees`)
|
||||||
|
).to.eventually.be.rejected.and.eql({
|
||||||
|
code: 400,
|
||||||
|
error: 'BadRequest',
|
||||||
|
message: t('invalidReqParams'),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns an empty array if there are no objections', async () => {
|
||||||
|
let receiver = await generateUser();
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
user.get(`/members/${receiver._id}/objections/send-private-message`)
|
||||||
|
).to.eventually.be.fulfilled.and.eql([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns an array of objections if any exist', async () => {
|
||||||
|
let receiver = await generateUser({'inbox.blocks': [user._id]});
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
user.get(`/members/${receiver._id}/objections/send-private-message`)
|
||||||
|
).to.eventually.be.fulfilled.and.eql([
|
||||||
|
t('notAuthorizedToSendMessageToThisUser'),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -82,6 +82,20 @@ describe('POST /members/send-private-message', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('returns an error when chat privileges are revoked', async () => {
|
||||||
|
let userWithChatRevoked = await generateUser({'flags.chatRevoked': true});
|
||||||
|
let receiver = await generateUser();
|
||||||
|
|
||||||
|
await expect(userWithChatRevoked.post('/members/send-private-message', {
|
||||||
|
message: messageToSend,
|
||||||
|
toUserId: receiver._id,
|
||||||
|
})).to.eventually.be.rejected.and.eql({
|
||||||
|
code: 401,
|
||||||
|
error: 'NotAuthorized',
|
||||||
|
message: t('chatPrivilegesRevoked'),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('sends a private message to a user', async () => {
|
it('sends a private message to a user', async () => {
|
||||||
let receiver = await generateUser();
|
let receiver = await generateUser();
|
||||||
|
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ describe('POST /members/transfer-gems', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns error when to user is not found', async () => {
|
it('returns error when recipient is not found', async () => {
|
||||||
await expect(userToSendMessage.post('/members/transfer-gems', {
|
await expect(userToSendMessage.post('/members/transfer-gems', {
|
||||||
message,
|
message,
|
||||||
gemAmount,
|
gemAmount,
|
||||||
@@ -55,7 +55,7 @@ describe('POST /members/transfer-gems', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns error when to user attempts to send gems to themselves', async () => {
|
it('returns error when user attempts to send gems to themselves', async () => {
|
||||||
await expect(userToSendMessage.post('/members/transfer-gems', {
|
await expect(userToSendMessage.post('/members/transfer-gems', {
|
||||||
message,
|
message,
|
||||||
gemAmount,
|
gemAmount,
|
||||||
@@ -67,6 +67,64 @@ describe('POST /members/transfer-gems', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('returns error when recipient has blocked the sender', async () => {
|
||||||
|
let receiverWhoBlocksUser = await generateUser({'inbox.blocks': [userToSendMessage._id]});
|
||||||
|
|
||||||
|
await expect(userToSendMessage.post('/members/transfer-gems', {
|
||||||
|
message,
|
||||||
|
gemAmount,
|
||||||
|
toUserId: receiverWhoBlocksUser._id,
|
||||||
|
})).to.eventually.be.rejected.and.eql({
|
||||||
|
code: 401,
|
||||||
|
error: 'NotAuthorized',
|
||||||
|
message: t('notAuthorizedToSendMessageToThisUser'),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns error when sender has blocked recipient', async () => {
|
||||||
|
let sender = await generateUser({'inbox.blocks': [receiver._id]});
|
||||||
|
|
||||||
|
await expect(sender.post('/members/transfer-gems', {
|
||||||
|
message,
|
||||||
|
gemAmount,
|
||||||
|
toUserId: receiver._id,
|
||||||
|
})).to.eventually.be.rejected.and.eql({
|
||||||
|
code: 401,
|
||||||
|
error: 'NotAuthorized',
|
||||||
|
message: t('notAuthorizedToSendMessageToThisUser'),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns an error when chat privileges are revoked', async () => {
|
||||||
|
let userWithChatRevoked = await generateUser({'flags.chatRevoked': true});
|
||||||
|
|
||||||
|
await expect(userWithChatRevoked.post('/members/transfer-gems', {
|
||||||
|
message,
|
||||||
|
gemAmount,
|
||||||
|
toUserId: receiver._id,
|
||||||
|
})).to.eventually.be.rejected.and.eql({
|
||||||
|
code: 401,
|
||||||
|
error: 'NotAuthorized',
|
||||||
|
message: t('chatPrivilegesRevoked'),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('works when only the recipient\'s chat privileges are revoked', async () => {
|
||||||
|
let receiverWithChatRevoked = await generateUser({'flags.chatRevoked': true});
|
||||||
|
|
||||||
|
await expect(userToSendMessage.post('/members/transfer-gems', {
|
||||||
|
message,
|
||||||
|
gemAmount,
|
||||||
|
toUserId: receiverWithChatRevoked._id,
|
||||||
|
})).to.eventually.be.fulfilled;
|
||||||
|
|
||||||
|
let updatedReceiver = await receiverWithChatRevoked.get('/user');
|
||||||
|
let updatedSender = await userToSendMessage.get('/user');
|
||||||
|
|
||||||
|
expect(updatedReceiver.balance).to.equal(gemAmount / 4);
|
||||||
|
expect(updatedSender.balance).to.equal(0);
|
||||||
|
});
|
||||||
|
|
||||||
it('returns error when there is no gemAmount', async () => {
|
it('returns error when there is no gemAmount', async () => {
|
||||||
await expect(userToSendMessage.post('/members/transfer-gems', {
|
await expect(userToSendMessage.post('/members/transfer-gems', {
|
||||||
message,
|
message,
|
||||||
@@ -144,7 +202,7 @@ describe('POST /members/transfer-gems', () => {
|
|||||||
expect(updatedSender.balance).to.equal(0);
|
expect(updatedSender.balance).to.equal(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not requrie a message', async () => {
|
it('does not require a message', async () => {
|
||||||
await userToSendMessage.post('/members/transfer-gems', {
|
await userToSendMessage.post('/members/transfer-gems', {
|
||||||
gemAmount,
|
gemAmount,
|
||||||
toUserId: receiver._id,
|
toUserId: receiver._id,
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import {generateUser} from '../../../../../helpers/api-integration/v3';
|
||||||
|
import applePayments from '../../../../../../website/server/libs/applePayments';
|
||||||
|
|
||||||
|
describe('payments : apple #cancelSubscribe', () => {
|
||||||
|
let endpoint = '/iap/ios/subscribe/cancel?noRedirect=true';
|
||||||
|
let user;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
user = await generateUser();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('success', () => {
|
||||||
|
let cancelStub;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
cancelStub = sinon.stub(applePayments, 'cancelSubscribe').returnsPromise().resolves({});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
applePayments.cancelSubscribe.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('cancels the subscription', async () => {
|
||||||
|
user = await generateUser({
|
||||||
|
'profile.name': 'sender',
|
||||||
|
'purchased.plan.paymentMethod': 'Apple',
|
||||||
|
'purchased.plan.customerId': 'customer-id',
|
||||||
|
'purchased.plan.planId': 'basic_3mo',
|
||||||
|
'purchased.plan.lastBillingDate': new Date(),
|
||||||
|
balance: 2,
|
||||||
|
});
|
||||||
|
|
||||||
|
await user.get(endpoint);
|
||||||
|
|
||||||
|
expect(cancelStub).to.be.calledOnce;
|
||||||
|
expect(cancelStub.args[0][0]._id).to.eql(user._id);
|
||||||
|
expect(cancelStub.args[0][1]['x-api-key']).to.eql(user.apiToken);
|
||||||
|
expect(cancelStub.args[0][1]['x-api-user']).to.eql(user._id);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import {generateUser} from '../../../../../helpers/api-integration/v3';
|
||||||
|
import applePayments from '../../../../../../website/server/libs/applePayments';
|
||||||
|
|
||||||
|
describe('payments : apple #verify', () => {
|
||||||
|
let endpoint = '/iap/ios/verify';
|
||||||
|
let user;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
user = await generateUser();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('success', () => {
|
||||||
|
let verifyStub;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
verifyStub = sinon.stub(applePayments, 'verifyGemPurchase').returnsPromise().resolves({});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
applePayments.verifyGemPurchase.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('makes a purchase', async () => {
|
||||||
|
user = await generateUser({
|
||||||
|
balance: 2,
|
||||||
|
});
|
||||||
|
|
||||||
|
await user.post(endpoint, {
|
||||||
|
transaction: {
|
||||||
|
receipt: 'receipt',
|
||||||
|
}});
|
||||||
|
|
||||||
|
expect(verifyStub).to.be.calledOnce;
|
||||||
|
expect(verifyStub.args[0][0]._id).to.eql(user._id);
|
||||||
|
expect(verifyStub.args[0][1]).to.eql('receipt');
|
||||||
|
expect(verifyStub.args[0][2]['x-api-key']).to.eql(user.apiToken);
|
||||||
|
expect(verifyStub.args[0][2]['x-api-user']).to.eql(user._id);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
import {generateUser, translate as t} from '../../../../../helpers/api-integration/v3';
|
||||||
|
import applePayments from '../../../../../../website/server/libs/applePayments';
|
||||||
|
|
||||||
|
describe('payments : apple #subscribe', () => {
|
||||||
|
let endpoint = '/iap/ios/subscribe';
|
||||||
|
let user;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
user = await generateUser();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('verifies sub key', async () => {
|
||||||
|
await expect(user.post(endpoint)).to.eventually.be.rejected.and.eql({
|
||||||
|
code: 400,
|
||||||
|
error: 'BadRequest',
|
||||||
|
message: t('missingSubscriptionCode'),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('success', () => {
|
||||||
|
let subscribeStub;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
subscribeStub = sinon.stub(applePayments, 'subscribe').returnsPromise().resolves({});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
applePayments.subscribe.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('makes a purchase', async () => {
|
||||||
|
user = await generateUser({
|
||||||
|
'profile.name': 'sender',
|
||||||
|
'purchased.plan.customerId': 'customer-id',
|
||||||
|
'purchased.plan.planId': 'basic_3mo',
|
||||||
|
'purchased.plan.lastBillingDate': new Date(),
|
||||||
|
balance: 2,
|
||||||
|
});
|
||||||
|
|
||||||
|
let sku = 'com.habitrpg.ios.habitica.subscription.3month';
|
||||||
|
|
||||||
|
await user.post(endpoint, {
|
||||||
|
sku,
|
||||||
|
receipt: 'receipt',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(subscribeStub).to.be.calledOnce;
|
||||||
|
expect(subscribeStub.args[0][0]).to.eql(sku);
|
||||||
|
expect(subscribeStub.args[0][1]._id).to.eql(user._id);
|
||||||
|
expect(subscribeStub.args[0][2]).to.eql('receipt');
|
||||||
|
expect(subscribeStub.args[0][3]['x-api-key']).to.eql(user.apiToken);
|
||||||
|
expect(subscribeStub.args[0][3]['x-api-user']).to.eql(user._id);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -23,6 +23,7 @@ describe('payments : google #cancelSubscribe', () => {
|
|||||||
it('makes a purchase', async () => {
|
it('makes a purchase', async () => {
|
||||||
user = await generateUser({
|
user = await generateUser({
|
||||||
'profile.name': 'sender',
|
'profile.name': 'sender',
|
||||||
|
'purchased.plan.paymentMethod': 'Google',
|
||||||
'purchased.plan.customerId': 'customer-id',
|
'purchased.plan.customerId': 'customer-id',
|
||||||
'purchased.plan.planId': 'basic_3mo',
|
'purchased.plan.planId': 'basic_3mo',
|
||||||
'purchased.plan.lastBillingDate': new Date(),
|
'purchased.plan.lastBillingDate': new Date(),
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ describe('payments : paypal #checkout', () => {
|
|||||||
balance: 2,
|
balance: 2,
|
||||||
});
|
});
|
||||||
|
|
||||||
await user.get(endpoint);
|
await user.get(`${endpoint}?noRedirect=true`);
|
||||||
|
|
||||||
expect(checkoutStub).to.be.calledOnce;
|
expect(checkoutStub).to.be.calledOnce;
|
||||||
expect(checkoutStub.args[0][0].gift).to.eql(undefined);
|
expect(checkoutStub.args[0][0].gift).to.eql(undefined);
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ describe('payments : paypal #checkoutSuccess', () => {
|
|||||||
balance: 2,
|
balance: 2,
|
||||||
});
|
});
|
||||||
|
|
||||||
await user.get(`${endpoint}?PayerID=${customerId}&paymentId=${paymentId}`);
|
await user.get(`${endpoint}?PayerID=${customerId}&paymentId=${paymentId}&noRedirect=true`);
|
||||||
|
|
||||||
expect(checkoutSuccessStub).to.be.calledOnce;
|
expect(checkoutSuccessStub).to.be.calledOnce;
|
||||||
|
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ describe('payments : paypal #subscribe', () => {
|
|||||||
balance: 2,
|
balance: 2,
|
||||||
});
|
});
|
||||||
|
|
||||||
await user.get(`${endpoint}?sub=${subKey}`);
|
await user.get(`${endpoint}?sub=${subKey}&noRedirect=true`);
|
||||||
|
|
||||||
expect(subscribeStub).to.be.calledOnce;
|
expect(subscribeStub).to.be.calledOnce;
|
||||||
|
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ describe('payments : paypal #subscribeCancel', () => {
|
|||||||
balance: 2,
|
balance: 2,
|
||||||
});
|
});
|
||||||
|
|
||||||
await user.get(endpoint);
|
await user.get(`${endpoint}?noRedirect=true`);
|
||||||
|
|
||||||
expect(subscribeCancelStub).to.be.calledOnce;
|
expect(subscribeCancelStub).to.be.calledOnce;
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ describe('payments : paypal #subscribeSuccess', () => {
|
|||||||
balance: 2,
|
balance: 2,
|
||||||
});
|
});
|
||||||
|
|
||||||
await user.get(`${endpoint}?token=${token}`);
|
await user.get(`${endpoint}?token=${token}&noRedirect=true`);
|
||||||
|
|
||||||
expect(subscribeSuccessStub).to.be.calledOnce;
|
expect(subscribeSuccessStub).to.be.calledOnce;
|
||||||
|
|
||||||
|
|||||||
25
test/api/v3/integration/shops/GET-shops_backgrounds.test.js
Normal file
25
test/api/v3/integration/shops/GET-shops_backgrounds.test.js
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import {
|
||||||
|
generateUser,
|
||||||
|
translate as t,
|
||||||
|
} from '../../../../helpers/api-integration/v3';
|
||||||
|
|
||||||
|
describe('GET /shops/backgrounds', () => {
|
||||||
|
let user;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
user = await generateUser();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns a valid shop object', async () => {
|
||||||
|
let shop = await user.get('/shops/backgrounds');
|
||||||
|
expect(shop.identifier).to.equal('backgroundShop');
|
||||||
|
expect(shop.text).to.eql(t('backgroundShop'));
|
||||||
|
expect(shop.notes).to.eql(t('backgroundShopText'));
|
||||||
|
expect(shop.imageName).to.equal('background_shop');
|
||||||
|
expect(shop.sets).to.be.an('array');
|
||||||
|
|
||||||
|
let sets = shop.sets.map(set => set.identifier);
|
||||||
|
expect(sets).to.include('incentiveBackgrounds');
|
||||||
|
expect(sets).to.include('backgrounds062014');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -208,6 +208,20 @@ describe('POST /tasks/:id/score/:direction', () => {
|
|||||||
expect(task.completed).to.equal(false);
|
expect(task.completed).to.equal(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('computes isDue', async () => {
|
||||||
|
await user.post(`/tasks/${daily._id}/score/up`);
|
||||||
|
let task = await user.get(`/tasks/${daily._id}`);
|
||||||
|
|
||||||
|
expect(task.isDue).to.equal(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('computes nextDue', async () => {
|
||||||
|
await user.post(`/tasks/${daily._id}/score/up`);
|
||||||
|
let task = await user.get(`/tasks/${daily._id}`);
|
||||||
|
|
||||||
|
expect(task.nextDue.length).to.eql(6);
|
||||||
|
});
|
||||||
|
|
||||||
it('scores up daily even if it is already completed'); // Yes?
|
it('scores up daily even if it is already completed'); // Yes?
|
||||||
|
|
||||||
it('scores down daily even if it is already uncompleted'); // Yes?
|
it('scores down daily even if it is already uncompleted'); // Yes?
|
||||||
@@ -315,6 +329,30 @@ describe('POST /tasks/:id/score/:direction', () => {
|
|||||||
|
|
||||||
expect(updatedUser.stats.gp).to.be.greaterThan(user.stats.gp);
|
expect(updatedUser.stats.gp).to.be.greaterThan(user.stats.gp);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('adds score notes to task', async () => {
|
||||||
|
let scoreNotesString = 'test-notes';
|
||||||
|
|
||||||
|
await user.post(`/tasks/${habit._id}/score/up`, {
|
||||||
|
scoreNotes: scoreNotesString,
|
||||||
|
});
|
||||||
|
let updatedTask = await user.get(`/tasks/${habit._id}`);
|
||||||
|
|
||||||
|
expect(updatedTask.history[0].scoreNotes).to.eql(scoreNotesString);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('errors when score notes are too large', async () => {
|
||||||
|
let scoreNotesString = new Array(258).join('a');
|
||||||
|
|
||||||
|
await expect(user.post(`/tasks/${habit._id}/score/up`, {
|
||||||
|
scoreNotes: scoreNotesString,
|
||||||
|
}))
|
||||||
|
.to.eventually.be.rejected.and.eql({
|
||||||
|
code: 401,
|
||||||
|
error: 'NotAuthorized',
|
||||||
|
message: t('taskScoreNotesTooLong'),
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
context('reward', () => {
|
context('reward', () => {
|
||||||
|
|||||||
@@ -496,6 +496,8 @@ describe('POST /tasks/user', () => {
|
|||||||
frequency: 'daily',
|
frequency: 'daily',
|
||||||
everyX: 5,
|
everyX: 5,
|
||||||
startDate: now,
|
startDate: now,
|
||||||
|
daysOfMonth: [15],
|
||||||
|
weeksOfMonth: [3],
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(task.userId).to.equal(user._id);
|
expect(task.userId).to.equal(user._id);
|
||||||
@@ -504,7 +506,11 @@ describe('POST /tasks/user', () => {
|
|||||||
expect(task.type).to.eql('daily');
|
expect(task.type).to.eql('daily');
|
||||||
expect(task.frequency).to.eql('daily');
|
expect(task.frequency).to.eql('daily');
|
||||||
expect(task.everyX).to.eql(5);
|
expect(task.everyX).to.eql(5);
|
||||||
|
expect(task.daysOfMonth).to.eql([15]);
|
||||||
|
expect(task.weeksOfMonth).to.eql([3]);
|
||||||
expect(new Date(task.startDate)).to.eql(now);
|
expect(new Date(task.startDate)).to.eql(now);
|
||||||
|
expect(task.isDue).to.be.true;
|
||||||
|
expect(task.nextDue.length).to.eql(6);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('creates multiple dailys', async () => {
|
it('creates multiple dailys', async () => {
|
||||||
@@ -609,6 +615,18 @@ describe('POST /tasks/user', () => {
|
|||||||
expect((new Date(task.startDate)).getDay()).to.eql(today);
|
expect((new Date(task.startDate)).getDay()).to.eql(today);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('returns an error if the start date is empty', async () => {
|
||||||
|
await expect(user.post('/tasks/user', {
|
||||||
|
text: 'test daily',
|
||||||
|
type: 'daily',
|
||||||
|
startDate: '',
|
||||||
|
})).to.eventually.be.rejected.and.eql({
|
||||||
|
code: 400,
|
||||||
|
error: 'BadRequest',
|
||||||
|
message: 'daily validation failed',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('can create checklists', async () => {
|
it('can create checklists', async () => {
|
||||||
let task = await user.post('/tasks/user', {
|
let task = await user.post('/tasks/user', {
|
||||||
text: 'test daily',
|
text: 'test daily',
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user