Compare commits

...

400 Commits

Author SHA1 Message Date
Sabe Jones
07e5bf1437 4.78.0 2018-12-31 23:48:21 +00:00
Sabe Jones
d2ca738256 chore(i18n): update locales 2018-12-31 23:48:11 +00:00
Sabe Jones
f7983f39eb feat(event): New Year's 2018-19 2018-12-31 17:44:13 -06:00
Sabe Jones
82d0e737a6 4.77.6 2018-12-27 15:47:36 +00:00
Sabe Jones
f8213aaf1b chore(i18n): update locales 2018-12-27 15:47:25 +00:00
Matteo Pagliazzi
2335ad4167 fix(gems modal): remove duplicate id property, fix #10919 2018-12-27 15:04:14 +01:00
Sabe Jones
88f28188a1 4.77.5 2018-12-21 20:45:20 +00:00
Sabe Jones
fce5be2e8f chore(i18n): update locales 2018-12-21 20:44:16 +00:00
Sabe Jones
1d68cbfaa2 chore(news): more Bailey 2018-12-21 14:42:11 -06:00
Sabe Jones
a44222a350 4.77.4 2018-12-20 16:37:04 -06:00
Sabe Jones
4d2510e322 fix(sprites): add missing dragon icon 2018-12-20 16:36:57 -06:00
Sabe Jones
3b5ed33e03 4.77.3 2018-12-20 21:29:58 +00:00
Sabe Jones
0a6bf92b6b chore(i18n): update locales 2018-12-20 21:29:24 +00:00
Sabe Jones
05ae40bc2e fix(content): correct timeframe tag for string 2018-12-20 15:26:17 -06:00
Sabe Jones
57e96ea092 4.77.2 2018-12-20 12:14:38 -06:00
Sabe Jones
ea49b5b8e0 fix(content): add set string for Dec Mystery 2018-12-20 12:14:28 -06:00
Sabe Jones
11a5de714a 4.77.1 2018-12-20 17:25:33 +00:00
Sabe Jones
f74b4d3e73 Merge branch 'develop' into release 2018-12-20 17:24:59 +00:00
Sabe Jones
ab6fdb99af 4.77.0 2018-12-20 17:23:29 +00:00
Sabe Jones
e3efa557dd chore(i18n): update locales 2018-12-20 17:22:57 +00:00
Sabe Jones
a6cea47789 feat(event): Winter Wonderland 2018 2018-12-20 11:11:50 -06:00
Sabe Jones
5a200a88bd chore(sprites): compile 2018-12-20 09:42:04 -06:00
Sabe Jones
545499ea0b WIP(event): partial content build 2018-12-20 09:40:54 -06:00
Sabe Jones
3e7f4229ec Merge branch 'release' into develop 2018-12-19 13:03:20 +00:00
Sabe Jones
56d5f77b6e 4.76.1 2018-12-19 13:02:58 +00:00
Sabe Jones
efdf5a2e16 chore(i18n): update locales 2018-12-19 13:01:40 +00:00
Sabe Jones
add3a2887f fix(css): restore close svg defaults 2018-12-19 06:58:56 -06:00
Sabe Jones
0ed7c7596a Merge branch 'release' into develop 2018-12-18 21:32:39 +00:00
Sabe Jones
55a452694f 4.76.0 2018-12-18 21:32:12 +00:00
Sabe Jones
cd4d5f83ff chore(i18n): update locales 2018-12-18 21:31:56 +00:00
Sabe Jones
8220199e49 Gift 1 Get 1 Promo 2018-19! (#10915)
* feat(subscription): promo banner in modal

* feat(subscription): promo banner on main page

* fix(banners): remove extraneous margin adjustment

* fix(banners): various

* feat(promotion): gift 1, get 1

* fix(promo): various

* chore(promo): add Bailey

* fix(promo): use different email template for promo beneficiary

* fix(promo): turns out Winter is meaningful
2018-12-18 15:28:53 -06:00
Sabe Jones
bd1f6918ba fix(sprites): wolf and penguin tweaks 2018-12-18 12:22:46 -06:00
Matteo Pagliazzi
5b21e62647 fix(i18n): format 2018-12-18 15:02:03 +01:00
Matteo Pagliazzi
5e76d6df21 fix(i18n): position of $ sign 2018-12-17 13:12:35 +01:00
Matteo Pagliazzi
7348145b7d fix(success-modal): misc fixes 2018-12-16 15:39:37 +01:00
Sabe Jones
ba2832d21f 4.75.5 2018-12-14 21:22:05 +00:00
Sabe Jones
688f5084a1 chore(i18n): update locales 2018-12-14 21:19:56 +00:00
Sabe Jones
d5955b8889 Merge branch 'develop' into release 2018-12-14 15:15:06 -06:00
Sabe Jones
01fd17ee3f fix(script): revert email query 2018-12-14 15:14:50 -06:00
Sabe Jones
979497dd35 fix(deletion): user delete bugs
Correct lookup in GDPR script, and address a TypeError when deleting a user with no tasks
2018-12-14 00:54:59 +00:00
Sabe Jones
1d3db244ba 4.75.4 2018-12-11 18:26:58 -06:00
Sabe Jones
80ca074352 fix(deploy): correct Dockerfile 2018-12-11 18:26:07 -06:00
Sabe Jones
6d4f9e0759 4.75.3 2018-12-11 20:48:40 +00:00
Sabe Jones
d096695559 Usernames Misc: Bulk Email and New Party Modal (#10898)
* feat(migrations): genericize bulk email

* chore(migrations): archive one-offs

* feat(usernames): change create party modal to copy username

* fix(modal): styling

* fix(modal): add Chrome clipboard implementation
2018-12-11 20:48:34 +00:00
Sabe Jones
a2c8b8b05c Usernames Misc: Bulk Email and New Party Modal (#10898)
* feat(migrations): genericize bulk email

* chore(migrations): archive one-offs

* feat(usernames): change create party modal to copy username

* fix(modal): styling

* fix(modal): add Chrome clipboard implementation
2018-12-11 14:47:50 -06:00
Sabe Jones
8750701c08 Merge branch 'release' into develop 2018-12-11 20:45:44 +00:00
Sabe Jones
900676bf0a 4.75.2 2018-12-11 20:45:15 +00:00
Sabe Jones
e219daf44c chore(i18n): update locales 2018-12-11 20:44:53 +00:00
Sabe Jones
1b12a6b51f feat(content): Bird Buddies Bundle 2018-12-11 14:41:09 -06:00
Matteo Pagliazzi
8441b0a3d6 fix http auth env var 2018-12-09 20:12:50 +01:00
Matteo Pagliazzi
0667695390 Payments: Success Messages (#10903)
* fix v-once

* gems purchase success dialog

* amazon and stripe support for gems message, translations

* subscriptions success message

* subscriptions success message

* group plans success message and many fixes

* finish success messages for payments

* add success modal when gifting from balance
2018-12-09 20:08:01 +01:00
Matteo Pagliazzi
4d322c1bf6 Merge branch 'sukh0128-develop' into develop 2018-12-07 15:20:27 +01:00
Matteo Pagliazzi
a855ddacc7 Merge branch 'develop' of https://github.com/sukh0128/habitica into sukh0128-develop 2018-12-07 15:20:12 +01:00
Matteo Pagliazzi
9d48ef7322 Merge pull request #10896 from AyoubElk/patch-1
Fixed typo in folder path
2018-12-07 15:13:08 +01:00
Sabe Jones
2991f7acfb 4.75.1 2018-12-06 16:23:28 +00:00
Sabe Jones
9dbfb565bb chore(i18n): update locales 2018-12-06 16:22:06 +00:00
Sabe Jones
f42e22b58f Make env vars more palatable for containerization (#10895)
* refactor(env-vars): remove object/colon syntax

* fix(tests): correct config expectations
2018-12-06 10:13:49 -06:00
Ayoub El Khattabi
95f3315796 Fixed typo in folder path
The tast test:api:unit:watch should watch the path 'test/api/unit/**/*' instead of the non existent 'test/api/v3/unit/**/*'
2018-12-06 11:26:22 +00:00
Sabe Jones
18ab57eb91 Merge branch 'release' into develop 2018-12-05 20:44:02 +00:00
Sabe Jones
0dcbd8ccb8 4.75.0 2018-12-05 20:43:39 +00:00
Sabe Jones
ed82b46f7b chore(i18n): update locales 2018-12-05 20:43:23 +00:00
Sabe Jones
dcc3044685 chore(sprites): compile 2018-12-05 14:39:09 -06:00
Sabe Jones
5cd62d7052 feat(content): Armoire and BGs 2018-12 2018-12-05 14:38:53 -06:00
Erdenesukh Tsendjav
5e232d8c9f Merge branch 'develop' of github.com:sukh0128/habitica into develop 2018-12-04 17:29:16 -06:00
Erdenesukh Tsendjav
13793f8b3c revert 2018-12-04 17:28:27 -06:00
Erdenesukh Tsendjav
55f875f95a Delete package-lock.json 2018-12-04 17:04:55 -06:00
Erdenesukh Tsendjav
14576be374 merged 2018-12-04 16:03:11 -06:00
Erdenesukh Tsendjav
4cb2c26475 The istanbul command string in the gulp-tests.js file for each of the tests had to be changed to just the keyword istanbul instead of pointing to the istanbul file location on WINDOWS machines 2018-12-04 15:55:09 -06:00
Matteo Pagliazzi
b66a0b76ef Merge pull request #10886 from jeongjinhwi/develop
Accoount Deletion Feedback:update email #10880
2018-12-04 09:38:33 +01:00
Sabe Jones
dd313b17b5 fix(migration): correct push op 2018-12-03 21:06:23 -06:00
Sabe Jones
bb01475e02 Merge branch 'release' into develop 2018-12-03 22:54:08 +00:00
Sabe Jones
757959529b 4.74.7 2018-12-03 22:53:23 +00:00
Sabe Jones
71ad1957b1 chore(i18n): update locales 2018-12-03 22:52:42 +00:00
Sabe Jones
3ba5ea1d2d chore(news): Bailey 2018-12-03 16:50:02 -06:00
Sabe Jones
902da35f2b fix(migration): syntax 2018-12-03 22:06:58 +00:00
Sabe Jones
aaa16a9527 refactor(migrations): move Take This to new template
also fix linting error in server lib
2018-12-03 22:04:52 +00:00
Sabe Jones
b98e95ee45 chore(items): deactivate premium potions 2018-12-03 15:49:09 -06:00
Matteo Pagliazzi
757160d6b7 update karma to fix tests 2018-12-03 13:33:58 +01:00
Matteo Pagliazzi
4cf68eb018 update package-lock 2018-12-03 13:22:23 +01:00
Matteo Pagliazzi
e91d5e5664 Merge pull request #10863 from HabitRPG/payments/paypal-amazon-checkout
Paypal and Amazon: Improve checkout experience
2018-12-03 12:55:53 +01:00
Matteo Pagliazzi
b9e12aca3e success modal and fix redirects 2018-12-02 17:33:21 +01:00
Jeong Jin Hwi
53ca9475ee Accoount Deletion Feedback:update email #10880 2018-12-03 01:04:42 +09:00
Matteo Pagliazzi
e212842b50 fix redirects and basic success message 2018-12-02 15:44:52 +01:00
Matteo Pagliazzi
d2f7cba43d Merge branch 'develop' into payments/paypal-amazon-checkout 2018-12-02 14:47:56 +01:00
Matteo Pagliazzi
84558f79d6 Merge pull request #10885 from HabitRPG/greenkeeper/initial
Update dependencies to enable Greenkeeper 🌴
2018-12-02 14:46:11 +01:00
Matteo Pagliazzi
178e59f287 update npm files 2018-12-02 14:44:19 +01:00
greenkeeper[bot]
7acccc0763 chore(package): update dependencies 2018-12-02 13:36:14 +00:00
Matteo Pagliazzi
8ac21d2fd4 update package-lock.json 2018-12-01 18:53:12 +01:00
Matteo Pagliazzi
7873800f87 fix(kafka queue): remove leftover code 2018-12-01 12:16:34 +01:00
Sabe Jones
727041f020 fix(chat): escape regexes so parentheses etc. don't bust it 2018-11-30 16:19:22 -06:00
Sabe Jones
de0c62a37f Merge branch 'release' into develop 2018-11-30 20:28:23 +00:00
Sabe Jones
68f420991e 4.74.6 2018-11-30 20:27:53 +00:00
Sabe Jones
f211ebeb0a chore(i18n): update locales 2018-11-30 20:25:45 +00:00
Sabe Jones
b91ef9f539 chore(news): Last Chance Bailey
also give a few more days for items
2018-11-30 14:21:12 -06:00
Matteo Pagliazzi
ac0601630e Merge branch 'develop' into payments/paypal-amazon-checkout 2018-11-30 16:11:55 +01:00
Matteo Pagliazzi
3239491144 fix(lint): remove unused params 2018-11-30 16:04:56 +01:00
Sabe Jones
c3b0a73507 4.74.5 2018-11-29 14:52:02 +00:00
Sabe Jones
b218eb2c00 Merge remote-tracking branch 'origin/greenkeeper/update-to-node-10' into release
also remove Kafka
2018-11-29 14:51:40 +00:00
Sabe Jones
37d9f76fea 4.74.4 2018-11-29 07:48:06 -06:00
Matteo Pagliazzi
7f2e12ba23 remove memwatch-next, try/catch 2018-11-29 14:35:22 +01:00
Sabe Jones
21b43287e3 4.74.3 2018-11-29 12:11:35 +00:00
Sabe Jones
357b48dc8f fix(server): update package lock, attempt to log crashes 2018-11-29 12:11:03 +00:00
Matteo Pagliazzi
2cbd78b139 Merge branch 'develop' into payments/paypal-amazon-checkout 2018-11-29 10:55:13 +01:00
Sabe Jones
ee5dd5842b 4.74.2 2018-11-28 16:18:20 -06:00
Sabe Jones
074b8138de Merge branch 'develop' into release 2018-11-28 16:18:10 -06:00
Sabe Jones
cd0b9c0a96 4.74.1 2018-11-28 15:57:54 -06:00
Sabe Jones
a09516944d fix(packages): revert package lock 2018-11-28 15:57:36 -06:00
Sabe Jones
b82660823d Merge branch 'release' into develop 2018-11-28 20:57:37 +00:00
Sabe Jones
fde4402fbb 4.74.0 2018-11-28 20:57:14 +00:00
Sabe Jones
5fe0776074 chore(i18n): update locales 2018-11-28 20:55:35 +00:00
Sabe Jones
d218f316d3 chore(sprites): compile 2018-11-28 14:50:54 -06:00
Sabe Jones
f19e69948a feat(content): mystery items 2018-11 2018-11-28 14:50:43 -06:00
Sabe Jones
e6807d36b5 fix(analytics): record usernames 2018-11-28 13:02:48 -06:00
Phillip Thelen
1ece230621 remove console call 2018-11-28 12:52:08 -06:00
Phillip Thelen
d934d9d759 Move group chat analytics event to server 2018-11-28 12:52:00 -06:00
Matteo Pagliazzi
bf7fabb20a fix(amazon): add new env variable to specify environment 2018-11-28 11:07:06 +01:00
Sabe Jones
88a2f317d8 Merge branch 'release' into develop 2018-11-27 19:46:39 -06:00
SabreCat
a30c4379a6 fix(tests): correct string update errors 2018-11-28 01:00:46 +00:00
Matteo Pagliazzi
b509c6631d update package-lock.json 2018-11-27 22:19:25 +01:00
Matteo Pagliazzi
5a725fa4b0 update package-lock.json 2018-11-27 22:17:43 +01:00
Sabe Jones
dbe2143b7a fix(mystery-items): add missing set sprite 2018-11-26 14:23:18 -06:00
Sabe Jones
7b687280d7 Merge branch 'release' into develop 2018-11-26 19:10:05 +00:00
Sabe Jones
8db6c8bd4f 4.73.2 2018-11-26 19:09:42 +00:00
Sabe Jones
bf91dacb94 chore(i18n): update locales 2018-11-26 19:01:55 +00:00
Sabe Jones
33e0892e95 chore(event): end Thanksgiving tweaks 2018-11-26 12:59:23 -06:00
Phillip Thelen
42b146d5d0 Attach client to chat messages (#10845)
* Attach client to chat messages

* Word

* Design tweaks

* Fix potential error
2018-11-26 10:45:42 +01:00
Nathan Zimmerman
2bebaf2cf8 Fixes issue #10857 ("Tags have extra space at the bottom when they should be centered") (#10861)
* Fix for #10857 centered category tag text

* Fixes #10857 and #10856 display tag markdown.
2018-11-26 10:44:41 +01:00
Matteo Pagliazzi
6181328ac1 feat(footer): always show expanded footer (#10862) 2018-11-26 10:37:29 +01:00
Sabe Jones
c64d4b0914 4.73.1 2018-11-23 20:20:56 +00:00
Sabe Jones
f94fd0d69d chore(i18n): update locales 2018-11-23 20:17:56 +00:00
Sabe Jones
2cd66436bc Merge branch 'release' into develop 2018-11-22 18:05:28 -06:00
Sabe Jones
81a17738b8 4.73.0 2018-11-22 21:09:31 +00:00
Sabe Jones
b6b953ec46 chore(i18n): update locales 2018-11-22 21:09:20 +00:00
Sabe Jones
ab34c83a9d chore(sprites): compile 2018-11-22 15:05:33 -06:00
Sabe Jones
9cea86f4e0 feat(content): Turkey Day 2018 2018-11-22 15:05:03 -06:00
Sabe Jones
1b7a705bf9 Merge branch 'paglias/migration-no-recursion' into release 2018-11-22 13:44:46 -06:00
negue
e2c5b9058b more checks on the item.klass, also added the specialClass checks (#10859) 2018-11-22 14:35:34 +01:00
Matteo Pagliazzi
cc751960ac save state and close tab afetr success 2018-11-21 11:42:32 +01:00
Matteo Pagliazzi
433c73c9d3 paypal: restore url after purchase 2018-11-21 11:25:36 +01:00
Matteo Pagliazzi
680c2162a7 paypal: new redirects 2018-11-21 11:19:55 +01:00
Alys
a2b38ffb02 add two slurs - TRIGGER / CONTENT WARNING: assault, slurs, swearwords, etc 2018-11-21 19:48:50 +10:00
Matteo Pagliazzi
6395870c00 fix(stable): remove progress number from petItem 2018-11-21 10:35:59 +01:00
Sabe Jones
9562ba432f Merge branch 'release' into develop 2018-11-20 21:02:17 +00:00
Sabe Jones
8a3a83de37 4.72.0 2018-11-20 21:01:41 +00:00
Sabe Jones
745edd731d chore(i18n): update locales 2018-11-20 20:59:23 +00:00
Sabe Jones
53b195931c chore(sprites): compile 2018-11-20 14:55:56 -06:00
Sabe Jones
37ae467fff feat(content): Frost Hatching Potions 2018-11-20 14:55:45 -06:00
negue
d7d7d64b45 move computed-props to methods - refactor mountItem to use the states inside (#10853) 2018-11-20 12:28:26 +01:00
Matteo Pagliazzi
e76bdbd62d Fix for #10814, prevent ParallelSave errors (#10852)
* fix(group leave): prevent ParallelSave errors while leaving a group with multiple group or challenge tasks

* fix typo
2018-11-18 20:48:16 +01:00
Matteo Pagliazzi
067e869141 fix(chat): prevent duplicate messages, closes #10823 2018-11-18 19:38:56 +01:00
greenkeeper[bot]
51224a69d9 Update superagent to the latest version 🚀 (#10848)
* fix(package): update superagent to version 4.0.0

* chore(package): update lockfile package-lock.json
2018-11-18 19:24:39 +01:00
Dimini
f56018d46a Very large Guild member counts overflow the badge #10753 (#10812) 2018-11-17 11:06:26 +01:00
Ian Oxley
b846185f8a Set width on .custom-control-label (#10840)
Set `width: 100%` on the `.custom-control-label`.

Although `overflow-wrap: break-word` is set on the parent `.checklist-item` element, it doesn't seem to take effect unless a width is set on the label.
2018-11-17 11:01:36 +01:00
Nathanael Farley
ff81e55839 groupChatReceived webhook fix (#10802)
* Moved sendGroupChatReceivedWebhooks to group.sendChat function.

* Added test for new functionality.
2018-11-17 10:48:41 +01:00
Sabe Jones
9a3cdb5deb 4.71.0 2018-11-15 22:03:32 +00:00
Sabe Jones
47ad7305f5 chore(i18n): update locales 2018-11-15 22:02:46 +00:00
Sabe Jones
d9b5bbe2a9 chore(sprites): compile 2018-11-15 15:58:15 -06:00
Sabe Jones
c2fe04367f feat(content): Oddballs Bundle
Also includes one more tweak to @mention text highlighting
2018-11-15 15:58:07 -06:00
Sabe Jones
abcc77b7d6 fix(chat): more width tweakage 2018-11-14 16:46:03 -06:00
Sabe Jones
07cbf45265 fix(chat): less intrusive highlight and better margins 2018-11-14 16:31:35 -06:00
Sabe Jones
c035435476 4.70.0 2018-11-14 14:39:39 +00:00
Sabe Jones
89fdd8a8bb chore(i18n): update locales 2018-11-14 14:39:23 +00:00
Sabe Jones
d406da4081 chore(news): Bailey 2018-11-14 08:30:34 -06:00
Sabe Jones
d74786ef85 Merge branch 'sabrecat/usernames-master' into develop 2018-11-14 08:21:49 -06:00
Sabe Jones
64a3d08ce3 fix(tests): linting & more expects
Also one more tweak for invite validation responsiveness
2018-11-14 07:43:08 -06:00
SabreCat
f635f178da fix(tests): correct expects 2018-11-14 13:07:44 +00:00
Matteo Pagliazzi
1a7461a8a2 move the update username route to v3 (#10836) 2018-11-14 10:40:27 +01:00
Sabe Jones
cc13c4f28e fix(invites): more responsive validation 2018-11-13 19:50:07 -06:00
SabreCat
239f78674b fix(usernames): address failing tests 2018-11-14 01:44:09 +00:00
Sabe Jones
d691dee2ca fix(usernames): filter @ on server side for username lookup 2018-11-13 15:34:50 -06:00
Sabe Jones
481bd6727d fix(usernames): exclude unverified users during query 2018-11-13 15:13:41 -06:00
Sabe Jones
d0fc1e0751 fix(invites): show errors inline 2018-11-13 15:10:35 -06:00
Sabe Jones
b07dbb7752 Merge branch 'develop' into sabrecat/usernames-master 2018-11-13 14:15:23 -06:00
Sabe Jones
34e7690c38 fix(usernames): various
Disappearing input fields
Text replacement in @mentions
UUID visibility in profiles
Purple dot for @mentioned usernames
TypeError preventing RYA
Group Plan member list error
2018-11-13 14:14:02 -06:00
negue
eca7382545 prevent buying market gear if class doesn't match (#10818)
* prevent buying market gear if class doesn't match

* add test
2018-11-12 21:32:54 +01:00
Matteo Pagliazzi
be95cd967a upgrade gulp-imagemin, closes #10817 2018-11-10 12:29:26 +01:00
Matteo Pagliazzi
ce03f837c7 use lean and .update 2018-11-09 13:10:55 +01:00
Matteo Pagliazzi
808885425f select more fields 2018-11-09 13:04:25 +01:00
Matteo Pagliazzi
39a35f44ef fixes 2018-11-09 13:01:19 +01:00
Matteo Pagliazzi
2b2e1d4b9a rewrite mongoose migration to avoid using recursion 2018-11-09 12:58:39 +01:00
Sabe Jones
869411c0e9 fix(migration): improve model-based script 2018-11-08 16:55:49 -06:00
Sabe Jones
7484ecf729 Merge branch 'develop' into sabrecat/usernames-master 2018-11-08 15:13:28 -06:00
Sabe Jones
3265440bc4 4.69.2 2018-11-08 18:47:54 +00:00
Sabe Jones
acf514e9cb chore(i18n): update locales 2018-11-08 18:47:32 +00:00
Sabe Jones
2789d44dbf chore(news): Bailey 2018-11-08 12:43:50 -06:00
Sabe Jones
b579f31e9e fix(emails): correct unsub link handling 2018-11-08 18:09:51 +00:00
Sabe Jones
5e781017ab fix(script): correct import path and template slug 2018-11-08 10:29:37 -06:00
Sabe Jones
b48f850eac fix(script): don't send to users who have already opted out of notifs 2018-11-08 10:14:54 -06:00
Sabe Jones
5d6b6ed29a feat(usernames): follow-up email and setting for email opt-out 2018-11-08 10:00:25 -06:00
negue
7fbc68511b fix: special are not "AllowedToFeed" (#10808)
*  fix: special are not "AllowedToFeed"

* fix lint
2018-11-08 11:11:04 +01:00
Matteo Pagliazzi
ee2858199b update(http-proxy-middleware): update to 0.19, closes #10650 2018-11-08 11:08:42 +01:00
Sabe Jones
b1dd79f75c fix(invites): bogus validation errors 2018-11-07 15:58:17 -06:00
Sabe Jones
1ac4dd8171 feat(usernames): invite by username 2018-11-07 15:00:22 -06:00
Sabe Jones
4f86abd6b2 Merge branch 'develop' into sabrecat/usernames-master 2018-11-07 08:39:57 -06:00
Sabe Jones
23b0688abb Merge branch 'develop' into sabrecat/usernames-master 2018-11-06 19:56:45 -06:00
Sabe Jones
38efe83cc7 fix(usernames): various
Partial fixage for autocomplete @ing
Don't add username to chat message if user is unverified
Fix flying pets
Fix console error about avatar intro
2018-11-06 19:55:06 -06:00
Sabe Jones
2dadd74097 Merge branch 'release' into develop 2018-11-06 16:53:35 -06:00
Sabe Jones
9f494360ef Merge branch 'release' into develop 2018-11-06 15:02:24 -06:00
Sabe Jones
4122bbdecf 4.69.1 2018-11-06 15:02:03 -06:00
Sabe Jones
256a3abc26 fix(event): de-Festivalize class change modal 2018-11-06 15:01:57 -06:00
Sabe Jones
b7f3c0f389 Merge branch 'release' into develop 2018-11-06 20:43:12 +00:00
Sabe Jones
dca00bf4b7 4.69.0 2018-11-06 20:42:50 +00:00
Sabe Jones
d31c8913d3 chore(i18n): update locales 2018-11-06 20:40:54 +00:00
Sabe Jones
d839d57299 chore(sprites): compile 2018-11-06 14:32:55 -06:00
Sabe Jones
ccc3b4d337 feat(content): Armoire and Backgrounds 2018/11 2018-11-06 14:31:50 -06:00
Matteo Pagliazzi
7195ac15b9 fix(group-plan-tasks): show checkmark when task completed 2018-11-05 15:04:45 +01:00
Sabe Jones
a5ef6a129e fix(auth): new users should start verified 2018-11-03 16:03:53 -05:00
Sabe Jones
38f5d63d29 fix(likes): accessibility tweaks 2018-11-03 13:16:32 -05:00
Sabe Jones
43194b71ce fix(usernames): RYA positioning and contrib tier in PMs 2018-11-03 12:50:59 -05:00
Matteo Pagliazzi
e4a347a3cb update gulp-nodemon 2018-11-03 14:08:46 +01:00
Sabe Jones
7eaf3e04ab fix(modals): button styling 2018-11-02 16:48:35 -05:00
Sabe Jones
b6b03751c4 fix(modals): maybe got it?!? 2018-11-02 16:27:08 -05:00
Sabe Jones
818d5e4eb6 fix(modals): better stack??? 2018-11-02 14:58:32 -05:00
Sabe Jones
f871c7cf63 fix(modal): various 2018-11-02 14:26:39 -05:00
Kirsty
e9eddec0c4 check pet is hatchable before highlighting (#10797) 2018-11-02 17:16:11 +01:00
Nathanael Farley
a48a6a292d If user's cron will happen later today, start the task yesterday. (#10783)
* If user's cron will happen later today, start the task yesterday.

* Added default dayStart to taskDefaults.

* Removed the need to call shouldDo twice to calculate nextDue.

* Revert "Removed the need to call shouldDo twice to calculate nextDue."

This reverts commit e1467f2fc33cfb11e6a4fc667460df6a48b69d45.

* Removed defaults from taskDefault arguments.

* Got user from $store in copyAsTodoModal.vue.

* Fixed tests for taskDefaults to include mock user.

* Fix shouldDo tests when run in GMT timezone.

* Added test to taskDefault; added utcOffset to taskDefault.

* Replaced utcOffset with zone.

* Removed erroneous import.
2018-11-02 16:58:01 +01:00
Matteo Pagliazzi
12aef475c8 update package-lock.json 2018-11-02 12:10:49 +01:00
Sabe Jones
112e4e1d76 Merge branch 'develop' into sabrecat/usernames-master 2018-11-01 21:51:33 -05:00
Sabe Jones
90eebbcd70 Merge branch 'release' into develop 2018-11-02 02:50:25 +00:00
Sabe Jones
1ad9ba4e71 4.68.1 2018-11-02 02:50:05 +00:00
Sabe Jones
c42b72f8a8 chore(i18n): update locales 2018-11-02 02:46:22 +00:00
Sabe Jones
830c8d3104 chore(event): end Habitoween/Fall Fest
Also announce November challenges
2018-11-01 21:44:00 -05:00
Sabe Jones
86ae5f3e44 fix(inbox): don't show likes in inbox
Also remove convo list contrib styling for now
2018-11-01 19:34:57 -05:00
Sabe Jones
3922415314 fix(inbox): more UN display fixes 2018-11-01 17:41:27 -05:00
Sabe Jones
6c71abfac8 fix(inbox): display correct UN for outbound user 2018-11-01 16:05:14 -05:00
Sabe Jones
6ab08a7d52 fix(usernames): don't supply username in public fields if unverified 2018-11-01 15:32:40 -05:00
Sabe Jones
dc46127fc7 refactor(auth): only import needed validator module 2018-11-01 15:22:20 -05:00
Sabe Jones
b54f031acd fix(chat): replace autocomplete at @ 2018-11-01 15:17:01 -05:00
Sabe Jones
eafa2f8cdd Merge branch 'release' into sabrecat/usernames-master 2018-11-01 14:51:28 -05:00
Matteo Pagliazzi
1815d2b6d3 docker: use 10 2018-10-31 17:11:59 +01:00
Matteo Pagliazzi
14cba76ba8 Merge branch 'develop' into greenkeeper/update-to-node-10 2018-10-31 17:09:08 +01:00
greenkeeper[bot]
fe45940d46 fix(package): update ora to version 3.0.0 (#10529) 2018-10-31 16:59:47 +01:00
Matteo Pagliazzi
7dac53867b fix(settings): remove kicked from group from list of push notifications, fixes #10796 2018-10-31 14:42:11 +01:00
Sabe Jones
6f64cb7d9b Merge branch 'release' into develop 2018-10-30 21:26:39 +00:00
Sabe Jones
7d989bcf50 4.68.0 2018-10-30 21:26:19 +00:00
Sabe Jones
7bd29c2dd7 chore(i18n): update locales 2018-10-30 21:24:55 +00:00
Sabe Jones
9e10490102 chore(sprites): compile 2018-10-30 16:21:51 -05:00
Sabe Jones
5792bc0000 feat(content): Habitoween 2018 2018-10-30 16:21:42 -05:00
Matteo Pagliazzi
06812878b5 Merge branch 'negue/stable_img_states' into develop 2018-10-30 16:37:04 +01:00
Matteo Pagliazzi
8714c7d162 fix lint 2018-10-30 16:36:30 +01:00
Matteo Pagliazzi
e66f4e7812 Merge branch 'develop' into negue/stable_img_states 2018-10-30 16:35:30 +01:00
negue
c73f565f65 refactor animal methods / vue methods 2018-10-29 20:46:16 +01:00
Kirsty
82e21df943 fix filter checkboxes in seasonal shop (#10792) 2018-10-29 16:04:42 +01:00
Sabe Jones
18ed148320 Merge branch 'release' into develop 2018-10-28 20:46:13 +00:00
Sabe Jones
a5fc909f0d 4.67.1 2018-10-28 20:45:38 +00:00
Sabe Jones
30a5192e19 chore(i18n): update locales 2018-10-28 20:45:27 +00:00
Matteo Pagliazzi
79c0499672 Mongoose: use $type as the typeKey (#10789)
* use $type as the typeKey in mongoose

* fix and add tests
2018-10-28 15:42:48 -05:00
Matteo Pagliazzi
dadb752087 Mongoose: use $type as the typeKey (#10789)
* use $type as the typeKey in mongoose

* fix and add tests
2018-10-28 15:23:41 +01:00
Matteo Pagliazzi
37b29d3449 fix(tests): increase timeout 2018-10-28 13:14:49 +01:00
Matteo Pagliazzi
bb2ed249b9 fix deprecation warning for sinon.js 2018-10-28 12:44:34 +01:00
Sabe Jones
bb90dde1b6 feat(usernames): verification step during Justin intro for social users 2018-10-27 12:37:22 -05:00
Sabe Jones
5299c8d406 fix(comments): validator for emails, remove prefixes, allow length 1 2018-10-26 16:25:15 -05:00
Sabe Jones
8b81e38538 fix(PR): more cleanup 2018-10-26 15:48:53 -05:00
Sabe Jones
8e05a1b489 Merge branch 'develop' into sabrecat/usernames-master 2018-10-26 15:45:55 -05:00
Sabe Jones
aafcbe60a3 fix(PR): remove unrelated changes 2018-10-26 15:42:41 -05:00
Matteo Pagliazzi
56d1b77215 Upgrade sinon (#10773)
* upgrade sinon

* sinon changes

* fix unit tests
2018-10-26 18:15:28 +02:00
Nathanael Farley
61da558a5d Added explanatory webhook text to UI. (#10782)
* Added explanatory webhook text to UI.

* Made new webhook API info translateable.
2018-10-26 16:46:36 +02:00
Sabe Jones
490531cc76 4.67.0 2018-10-25 21:36:13 +00:00
Sabe Jones
8cd4c502bc chore(i18n): update locales 2018-10-25 21:35:58 +00:00
Sabe Jones
d8cacb653e chore(sprites): compile 2018-10-25 16:31:13 -05:00
Sabe Jones
59436a8bf7 feat(content): Mystery Items Oct 2018 2018-10-25 16:31:00 -05:00
Sabe Jones
6fade19f27 4.66.2 2018-10-25 20:32:06 +00:00
Sabe Jones
15d028a281 chore(i18n): update locales 2018-10-25 20:31:20 +00:00
Sabe Jones
95b283676a Merge branch 'sabrecat/username-display' into sabrecat/usernames-master 2018-10-25 05:32:41 -05:00
Sabe Jones
6e7e81206a fix(profile): better username/UUID styling 2018-10-25 05:29:02 -05:00
Sabe Jones
af74cc7c64 Merge branch 'sabrecat/username-display' into sabrecat/usernames-master 2018-10-24 20:01:00 -05:00
Sabe Jones
a9e2a17077 fix(chat): no min height on autocomplete dropdown 2018-10-24 19:59:54 -05:00
Sabe Jones
92057dbe17 Merge branch 'sabrecat/username-display' into sabrecat/usernames-master 2018-10-24 19:41:21 -05:00
Sabe Jones
b4ab525be5 fix(chat): better @ searching, no chat card borders 2018-10-24 19:39:50 -05:00
Sabe Jones
d3c464d5ea Merge branch 'sabrecat/force-username-modal' into sabrecat/usernames-master 2018-10-24 18:40:43 -05:00
Sabe Jones
804fe1c6d5 fix(usernames): various
z-index modals above Resting banner
force reload after verify username
add missing e-mail validation on frontpage
let Yesterdaily modal float behind username modal
2018-10-24 18:39:54 -05:00
Erdenesukh Tsendjav
3d757c7814 trying something 2018-10-24 16:31:46 -05:00
negue
3c5025a78e fix background empty / resetCallback - refactor pet methods/components 2018-10-24 20:43:15 +02:00
Sabe Jones
e028232527 Merge branch 'release' into develop 2018-10-24 18:35:55 +00:00
Sabe Jones
e3b270a62e 4.66.1 2018-10-24 18:35:34 +00:00
Sabe Jones
fceeacec3b chore(i18n): update locales 2018-10-24 18:35:27 +00:00
Matteo Pagliazzi
f93c67e57c fix(amazon gift): do not reset the gift amount when opening the amazon modal 2018-10-24 20:29:27 +02:00
Sabe Jones
192dc26fbe Merge branch 'release' into develop 2018-10-23 22:50:49 +00:00
Sabe Jones
1c1f270f64 4.66.0 2018-10-23 22:50:25 +00:00
Sabe Jones
483768f4a7 chore(i18n): update locales 2018-10-23 22:49:30 +00:00
SabreCat
65031cef3a chore(sprites): compile 2018-10-23 22:45:47 +00:00
Sabe Jones
2fc1f46359 Veteran Pet ladder award for users affected by username changes (#10765)
* feat(usernames): Veteran Pet ladder award for affected users

* feat(content): Vet Pet Bailey etc.
2018-10-23 17:38:30 -05:00
Matteo Pagliazzi
30fd530576 fix(tests): more timeouts fixes 2018-10-23 20:23:52 +02:00
Matteo Pagliazzi
f79999fde7 minor deps updates 2018-10-23 16:48:59 +02:00
Tressley Cahill
90d6e443ba Corrects the white bar above the header and updates the text styling (#10772) 2018-10-23 13:50:02 +02:00
Derek Kim
4ed1082558 fix for #10496: Newly subscribed accounts receive erroneous notification that they have Mystery Items (#10759)
* fix
Newly subscribed accounts receive erroneous notification that they have Mystery Items

* Added unit test for #10496

* Restored a previous unit test
2018-10-23 13:47:15 +02:00
Matteo Pagliazzi
00717eda76 fix(tests): increase timeout 2018-10-23 13:30:38 +02:00
Matteo Pagliazzi
d1b86e6c14 Remove code for Pusher (#10774)
* remove pusher

* fix linting
2018-10-23 13:25:52 +02:00
Matteo Pagliazzi
c813afba44 Upgrade mongoose (#10767)
* fix mongoose and upgrade

* fix more validations
2018-10-23 13:25:14 +02:00
Matteo Pagliazzi
d49db6d367 upgrade csv-stringify (#10776) 2018-10-23 13:22:22 +02:00
Matteo Pagliazzi
d6835aec56 upgrade method override (#10775) 2018-10-23 11:48:15 +02:00
Matteo Pagliazzi
960f7b5886 upgrade node-gcm (#10777) 2018-10-23 11:47:26 +02:00
Sabe Jones
cd9630332d Merge branch 'sabrecat/username-display' into sabrecat/usernames-master 2018-10-22 16:30:12 -05:00
Sabe Jones
ed21a37e5a feat(usernames): show in party header, profiles, inbox convo list 2018-10-22 16:29:47 -05:00
negue
16256ee190 fix issues 2018-10-22 21:22:26 +02:00
Matteo Pagliazzi
ff57e31f4f 4.65.7 2018-10-20 16:17:26 +02:00
Matteo Pagliazzi
6e21d154ae Do not throw an error when adding the same push device twice (#10770)
* do not throw an error when adding the same push device twice

* fix spelling

* fix linting
2018-10-20 14:52:02 +02:00
Sabe Jones
fdecc8ce16 Merge branch 'sabrecat/username-display' into sabrecat/usernames-master 2018-10-19 17:44:52 -05:00
Sabe Jones
3cc49f6637 fix(chat): match hyphen in @name regex 2018-10-19 17:44:18 -05:00
Sabe Jones
47f49f4256 Merge branch 'sabrecat/username-display' into sabrecat/usernames-master 2018-10-19 16:22:11 -05:00
Sabe Jones
4f4bb52360 fix(chat): padding adjustment and tooltip placement 2018-10-19 16:20:13 -05:00
Sabe Jones
3748b3046b Merge branch 'sabrecat/force-username-modal' into sabrecat/usernames-master 2018-10-19 16:04:23 -05:00
Sabe Jones
5cd0f56811 fix(usernames): let verify modal grow to content 2018-10-19 16:03:48 -05:00
Tressley Cahill
fe5beac91b Custom reward action background and font-color updates (#10769)
* fixes custom reward action background and font-color

* update to -10
2018-10-19 10:01:02 -05:00
Sabe Jones
52fd6a1451 4.65.6 2018-10-18 23:09:12 +00:00
Sabe Jones
ae445555e9 fix(groups): don't show add manager for non-groups and non-leaders 2018-10-18 18:08:30 -05:00
Sabe Jones
c4fc6671b4 4.65.5 2018-10-18 20:05:29 +00:00
Sabe Jones
e7a096158e chore(i18n): update locales 2018-10-18 20:05:15 +00:00
Sabe Jones
98473fcfaa chore(news): Bailey 2018-10-18 15:00:21 -05:00
Sabe Jones
e4300fc714 fix(registration): localize reg form placeholders 2018-10-18 14:48:46 -05:00
negue
456c5e57bc refactor petItem - pet image states 2018-10-18 19:58:14 +02:00
Matteo Pagliazzi
ffba435923 fix #10756: do not show push notification settings for email only notifications 2018-10-18 14:21:35 +02:00
Matteo Pagliazzi
1f44444a50 Fix subcriptions remaining time disappearing after cancelling (#10761)
* add hasCancelled method for group/user, prevent cancelling a subscription twice

* wip

* paypal: do not cancel a subscription twice

* make sure hasCancelled and hasNotCancelled return a boolean result
2018-10-18 12:14:07 +02:00
Sabe Jones
185b20995a Merge branch 'sabrecat/username-display' into sabrecat/usernames-master 2018-10-17 18:31:16 -05:00
Sabe Jones
fdf2e590ea fix(chat): better likes and display name font sizing 2018-10-17 18:28:58 -05:00
Sabe Jones
994123c387 chore(sprites): compile 2018-10-17 15:32:20 -05:00
Sabe Jones
273590716c Merge branch 'sabrecat/veteran-verified' into sabrecat/usernames-master 2018-10-17 15:29:44 -05:00
Sabe Jones
6818a094ee Merge branch 'sabrecat/username-display' into sabrecat/usernames-master 2018-10-17 15:29:35 -05:00
Sabe Jones
c99855cef4 Merge branch 'sabrecat/force-username-modal' into sabrecat/usernames-master 2018-10-17 15:29:23 -05:00
Sabe Jones
6845943ed0 feat(usernames): vet pet announcement 2018-10-17 15:27:54 -05:00
Sabe Jones
044fe17757 feat(usernames): Veteran Pet ladder award for affected users 2018-10-17 14:43:33 -05:00
Sabe Jones
ad0ede8d01 fix(model): don't break if auth.local undefined 2018-10-17 13:59:39 -05:00
Sabe Jones
23815e89e1 WIP(usernames): display in chat areas 2018-10-17 12:57:57 -05:00
Sabe Jones
061d990e39 Merge branch 'release' into develop 2018-10-15 20:20:55 +00:00
Sabe Jones
71f4e6bc08 4.65.4 2018-10-15 20:20:33 +00:00
Sabe Jones
659f160e22 chore(i18n): update locales 2018-10-15 20:20:25 +00:00
Trevor Ford
5f27bc5f90 Issue 10728: sort equipment by stat descending on Market page (#10734)
* sort equipment by stat descending in Market (issue #10728)

* fix sorting equipment by PER in Market (new issue?)

* move filter logic into method when sorting equipment in Market

* consolidate sorting in sortedGearItems() into one _orderBy call
2018-10-15 21:12:53 +02:00
negue
074837b274 check every armoire gear 'canOwn' method (#10760) 2018-10-15 20:11:28 +02:00
Sabe Jones
cfd19ac694 fix(usernames): various
Better modal positioning
Correct text colors for input field
Don't show "already taken" if username has other errors
2018-10-15 11:27:49 -05:00
SabreCat
0897ab5dc9 fix(settings): don't center align display name issues 2018-10-14 23:25:03 +00:00
Sabe Jones
5c6e8a7331 fix(usernames): add display name verification to site settings
also corrrect some validation logic in force-verify modal
2018-10-14 23:06:41 +00:00
Sabe Jones
c576c5261e fix(username): Add @ prepend 2018-10-14 12:21:52 -05:00
Sabe Jones
bbd98517ff fix(test): copypasta and string trouble 2018-10-13 21:36:14 -05:00
Sabe Jones
392b54aa7b fix(usernames): remove more ratzen fratzen dupe strings 2018-10-13 21:05:07 -05:00
Sabe Jones
60b26d4ec0 fix(test): string is miscapitalized :( 2018-10-13 20:56:09 -05:00
Matteo Pagliazzi
aa517e0ad6 Merge branch 'negue/modal-notifications' into develop 2018-10-13 21:18:30 +02:00
Matteo Pagliazzi
5ca489dee7 Merge branch 'develop' into negue/modal-notifications 2018-10-13 21:18:16 +02:00
Rene Cordier
fe39ef72ff Show accurate experience notifications (#10676)
* Show accurate experience notifications

Add unit tests for exp notifications

* use array to compute exp and lvl values for notification changes

* Add tests for user loosing xp cases
2018-10-13 20:24:23 +02:00
Carl Vuorinen
eee5f2f1df No matching Guilds/Challenges message (#10744)
* Display message on My Guilds page when filters dont' match anything

* Display message on Discover Guilds page when filters dont' match anything

* Display message on My Challenges page when filters dont' match anything

* Display message on Discover Challenges page when filters dont' match anything

* Don't show Load More button when there is nothing to load

* Fix Guild search

Previously was not possible to clear after searching
2018-10-13 20:19:03 +02:00
Sabe Jones
fd8572c28a Group Management Menu Fixes (#10704)
* fix(groups): more intelligent member actions

* fix(groups): further member action improvements

* fix(groups): don't show "Remove Manager" if user doesn't have authority

* fix(lint): bad if syntax

* fix(groups): unnecessary if on icon
2018-10-13 20:15:46 +02:00
Kirsty
f161987e1e check officialPinnedItems for gala gear in market (#10745) 2018-10-13 20:07:30 +02:00
aszlig
2304d970a5 api: Fix a few API documentation typos (#10749)
Just fixes a few syntactic errors and typos.

Signed-off-by: aszlig <aszlig@nix.build>
2018-10-13 20:03:40 +02:00
Sabe Jones
25ed05ab0a Analytics: clean up old A/B test code & add username verify flag (#10754)
* chore(analytics): clean up old A/B test code & add username verify

* fix(lint): more AB cleanup
2018-10-13 13:03:20 -05:00
Sabe Jones
fa1fef11d6 feat(usernames): modal to force verification 2018-10-13 12:53:16 -05:00
Sabe Jones
6f5b9ef119 fix(scripts): better error handling for script runner and GDPR 2018-10-12 15:27:31 +00:00
Sabe Jones
c64ea0a9a9 4.65.3 2018-10-11 21:05:46 +00:00
Sabe Jones
2e36b896d4 chore(i18n): update locales 2018-10-11 21:04:30 +00:00
Sabe Jones
6fe73d431e Merge branch 'develop' into release 2018-10-11 16:01:56 -05:00
Sabe Jones
998621cefe feat(content): fall avatar customization 2018-10-11 16:01:32 -05:00
Matteo Pagliazzi
67bb179c25 gifts: prevent users from sending the same gift twice by clicking many times on the Send button 2018-10-11 19:01:15 +02:00
Sabe Jones
c875861dab Merge branch 'release' into develop 2018-10-10 16:26:28 +00:00
Sabe Jones
418c18ddb2 4.65.2 2018-10-09 23:40:09 -05:00
Sabe Jones
0caab5c8d0 fix(news): missing footnote 2018-10-09 23:39:48 -05:00
Sabe Jones
218e65b04b Merge branch 'release' into develop 2018-10-09 21:33:49 -05:00
Sabe Jones
fcd7ba77a7 4.65.1 2018-10-09 21:32:58 -05:00
Sabe Jones
b0d177643c chore(sprites): compile 2018-10-09 21:32:38 -05:00
Sabe Jones
c0e0b10a95 Merge branch 'release' into develop 2018-10-10 01:20:18 +00:00
Sabe Jones
0bee2caf2e 4.65.0 2018-10-10 01:19:52 +00:00
Sabe Jones
e56d097b3a chore(i18n): update locales 2018-10-10 01:16:28 +00:00
Sabe Jones
8c63a9e31f feat(content): Alligator Pets and Spoopy Sporples 2018-10-09 20:12:12 -05:00
Sabe Jones
28ed9d8bcc fix(script): log, not warn, so all output goes to both stdout and tee 2018-10-09 20:27:52 +00:00
Matteo Pagliazzi
36ead77e0c Fixes group plan verify username (#10747)
Misc fixes
2018-10-09 20:07:50 +02:00
Robert Kojima
e7969987ec Guild textarea at list positioning (#10663)
* autocomplete dialog now has ternary operator to determine placement

* added min height to textbox

* fixed spacing according to travisCI

* heightToUse function now retrieves argument from props
2018-10-08 22:25:49 +02:00
titchimoto
97021e3422 Issue 10414 - Remove Member Option from View Party link. (#10639)
* Add remove member option from main task page

* Code refactor for remove member options

* code refactor to avoid loading party multiple times

* fix dispatch to ensure only pulling once from server
2018-10-08 22:22:09 +02:00
Carl Vuorinen
218d47d64a Don't show "no guilds" texts while loading (#10665)
* Don't show "no guilds" texts while loading

Unified styling of "no guilds" message with my challenges page
Fixes #10662

* Don't show "no challenges" texts while loading

Add loading indicator (similar to find challenges & my guilds pages)

* Change gray color

* Set challenge icon color
2018-10-08 22:18:11 +02:00
Rene Cordier
bdfc23717e Css font home page update (#10672)
* css font home page update

finish home page font change

* Small fixes on css font update on home page
2018-10-08 22:16:14 +02:00
Kirsty
464cd87736 Decrease mana when removing stat points from int (#10713)
* Decrease mana when removing stat points from int

* Revert "Decrease mana when removing stat points from int"

This reverts commit 5e25e13552.

* add mana when stat updates are saved

* don't allow users to deallocate saved stat points in the ui

* use flag to determine whether to add mana points

* add test for not adding mana points when flag is set

* Revert "add test for not adding mana points when flag is set"

This reverts commit 6e8ff36a79.

* Revert "use flag to determine whether to add mana points"

This reverts commit 274e2d0d33.

* Revert "add mana when stat updates are saved"

This reverts commit 422bd49191.

* move client side stat allocation to when save is pressed

* update displayed total stats during editing

* Fix lint errors
2018-10-08 22:11:26 +02:00
Kirsty
67a8eebb96 add errors for any param validation failures to the snackbar (#10724) 2018-10-08 21:54:05 +02:00
Kirsty
cfc0f6a3ac remove items with that have been lost from Class:None (#10735) 2018-10-08 21:38:16 +02:00
Matteo Pagliazzi
9f76db12bd update aws-sdk, in-app-purchase, fix #10733 and #10725 2018-10-08 10:55:41 +02:00
Sabe Jones
70192e4935 Scripts October 2018 (#10741)
* chore(scripts): BTS Challenge archive and username email jobbing

* refactor(migration): use batching and sendTxn

* fix(script): introduce delay for batching

* fix(migration): correct import, fix delay promise, slower batching

* fix(migration): add daterange

* WIP(script): deletion helper for GDPR

* fix(script): address code comments

* refactor(script): use for loop

* fix(script-runner): bad catch syntax

* fix(script-runner): oops I did it again

* fix(lint): name functions
2018-10-07 14:20:30 -05:00
Sabe Jones
5cd4ead9d1 Merge branch 'release' into develop 2018-10-06 14:36:10 +00:00
Sabe Jones
87cd000bb8 4.64.2 2018-10-06 14:35:48 +00:00
Sabe Jones
0de5d8273b chore(i18n): update locales 2018-10-06 14:35:39 +00:00
Sabe Jones
379898cc4d fix(sprites): add new spritesheet to app manifest 2018-10-06 09:33:35 -05:00
Sabe Jones
adeaa6c754 Merge branch 'release' into develop 2018-10-05 19:55:45 +00:00
Sabe Jones
539f0e33e2 4.64.1 2018-10-05 19:55:23 +00:00
Sabe Jones
405e053377 chore(i18n): update locales 2018-10-05 19:54:21 +00:00
Phillip Thelen
52fbb8f899 Verify username as valid if user is re-checking their current name (#10737)
* Verify username as valid if user is re-checking their current name

* Fix lint error and existingUser check.
2018-10-05 14:51:21 -05:00
Matteo Pagliazzi
c880596a77 Cleanup after inbox migration (#10487) 2018-10-05 19:34:42 +02:00
Matteo Pagliazzi
a35f04be46 migrations: move inbox migration to archive 2018-10-05 19:34:21 +02:00
negue
5632031f16 reload page if the user closes the modal or not clicking on the notification 2018-09-30 17:22:44 +02:00
Keith Holliday
1c51e62e43 Merged in develop 2018-09-10 09:42:51 -05:00
negue
07bc374078 fix ultimate gear notification length - allow longer notifications but with a-like border-radius 2018-08-27 20:08:09 +02:00
negue
c862bdb76a Merge branch 'develop' of https://github.com/HabitRPG/habitica into negue/modal-notifications
# Conflicts:
#	website/client/components/notifications.vue
2018-08-18 14:25:20 +02:00
negue
b596576c53 rollback death modal changes 2018-08-18 14:22:16 +02:00
Keith Holliday
f049d29d1b Added username invite 2018-08-16 17:40:53 -05:00
negue
9fd26a88ea Update notification.vue
remove duplicate methods props
2018-07-29 21:55:24 +02:00
negue
76860fe3f8 Merge branch 'develop' of https://github.com/HabitRPG/habitica into negue/modal-notifications
# Conflicts:
#	website/client/components/notifications.vue
2018-07-29 20:21:51 +02:00
negue
b16e700de5 auto revive 2018-07-29 20:12:30 +02:00
negue
b75e65f42d move modals to notifications (to open the modals) 2018-07-20 23:56:36 +02:00
Matteo Pagliazzi
fd700f92ae remove node 8 2018-06-18 10:17:38 +02:00
Matteo Pagliazzi
f41665f5a9 update deps 2018-06-18 10:04:03 +02:00
Matteo Pagliazzi
8b385c0b7b update deps 2018-06-18 10:01:09 +02:00
Matteo Pagliazzi
282f8db933 Merge branch 'develop' into greenkeeper/update-to-node-10 2018-06-18 09:57:39 +02:00
Matteo Pagliazzi
662b08c242 update engines 2018-06-18 09:57:35 +02:00
Matteo Pagliazzi
b7e601be16 Merge branch 'develop' into greenkeeper/update-to-node-10 2018-04-27 19:49:11 +02:00
greenkeeper[bot]
395676fcb1 Update engines to node 10 in package.json 2018-04-27 11:41:03 +00:00
greenkeeper[bot]
cc4df1c995 Update to node 10 in .nvmrc 2018-04-27 11:41:01 +00:00
greenkeeper[bot]
48eada2c37 Update to node 10 in .travis.yml 2018-04-27 11:40:58 +00:00
1105 changed files with 47565 additions and 40596 deletions

2
.nvmrc
View File

@@ -1 +1 @@
8
10

View File

@@ -1,6 +1,6 @@
language: node_js
node_js:
- '8'
- '10'
services:
- mongodb
cache:

View File

@@ -1,4 +1,4 @@
FROM node:8
FROM node:10
ENV ADMIN_EMAIL admin@habitica.com
ENV AMAZON_PAYMENTS_CLIENT_ID amzn1.application-oa2-client.68ed9e6904ef438fbc1bf86bf494056e

View File

@@ -1,4 +1,4 @@
FROM node:8
FROM node:10
# Install global packages
RUN npm install -g gulp-cli mocha

View File

@@ -1,120 +1,81 @@
{
"PORT":3000,
"ENABLE_CONSOLE_LOGS_IN_PROD":"false",
"IP":"0.0.0.0",
"WEB_CONCURRENCY":1,
"BASE_URL":"http://localhost:3000",
"FACEBOOK_KEY":"123456789012345",
"FACEBOOK_SECRET":"aaaabbbbccccddddeeeeffff00001111",
"GOOGLE_CLIENT_ID":"123456789012345",
"GOOGLE_CLIENT_SECRET":"aaaabbbbccccddddeeeeffff00001111",
"PLAY_API": {
"CLIENT_ID": "aaaabbbbccccddddeeeeffff00001111",
"CLIENT_SECRET": "aaaabbbbccccddddeeeeffff00001111",
"ACCESS_TOKEN":"aaaabbbbccccddddeeeeffff00001111",
"REFRESH_TOKEN":"aaaabbbbccccddddeeeeffff00001111"
},
"NODE_DB_URI":"mongodb://localhost/habitrpg",
"TEST_DB_URI":"mongodb://localhost/habitrpg_test",
"NODE_ENV":"development",
"ENABLE_CONSOLE_LOGS_IN_TEST": "false",
"CRON_SAFE_MODE":"false",
"CRON_SEMI_SAFE_MODE":"false",
"MAINTENANCE_MODE": "false",
"SESSION_SECRET":"YOUR SECRET HERE",
"SESSION_SECRET_KEY": "1234567891234567891234567891234567891234567891234567891234567891",
"SESSION_SECRET_IV": "12345678912345678912345678912345",
"ADMIN_EMAIL": "you@example.com",
"SMTP_USER":"user@example.com",
"SMTP_PASS":"password",
"SMTP_SERVICE":"Gmail",
"SMTP_HOST":"example.com",
"SMTP_PORT": 587,
"SMTP_TLS": true,
"STRIPE_API_KEY":"aaaabbbbccccddddeeeeffff00001111",
"STRIPE_PUB_KEY":"22223333444455556666777788889999",
"NEW_RELIC_LICENSE_KEY":"NEW_RELIC_LICENSE_KEY",
"NEW_RELIC_NO_CONFIG_FILE":"true",
"NEW_RELIC_APPLICATION_ID":"NEW_RELIC_APPLICATION_ID",
"NEW_RELIC_API_KEY":"NEW_RELIC_API_KEY",
"GA_ID": "GA_ID",
"AMPLITUDE_KEY": "AMPLITUDE_KEY",
"AMAZON_PAYMENTS": {
"SELLER_ID": "SELLER_ID",
"CLIENT_ID": "CLIENT_ID",
"MWS_KEY": "",
"MWS_SECRET": ""
},
"FLAG_REPORT_EMAIL": "email@mod.com,email2@mod.com",
"EMAIL_SERVER": {
"url": "http://example.com",
"authUser": "user",
"authPassword": "password"
},
"S3":{
"bucket":"bucket",
"accessKeyId":"accessKeyId",
"secretAccessKey":"secretAccessKey"
},
"SLACK_URL": "https://hooks.slack.com/services/some-url",
"TRANSIFEX_SLACK_CHANNEL": "transifex",
"PAYPAL":{
"billing_plans": {
"basic_earned":"basic_earned",
"basic_3mo":"basic_3mo",
"basic_6mo":"basic_6mo",
"google_6mo":"google_6mo",
"basic_12mo":"basic_12mo"
},
"mode":"sandbox",
"client_id":"client_id",
"client_secret":"client_secret",
"experience_profile_id": ""
},
"IAP_GOOGLE_KEYDIR": "/path/to/google/public/key/dir/",
"LOGGLY_TOKEN": "token",
"LOGGLY_CLIENT_TOKEN": "token",
"LOGGLY_ACCOUNT": "account",
"PUSH_CONFIGS": {
"GCM_SERVER_API_KEY": "",
"APN_ENABLED": "false",
"APN_KEY_ID": "xxxxxxxxxx",
"APN_KEY": "xxxxxxxxxx",
"APN_TEAM_ID": "aaabbbcccd",
"FCM_SERVER_API_KEY": ""
},
"SITE_HTTP_AUTH": {
"ENABLED": "false",
"USERNAME": "admin",
"PASSWORD": "password"
},
"PUSHER": {
"ENABLED": "false",
"APP_ID": "appId",
"KEY": "key",
"SECRET": "secret"
},
"SLACK": {
"FLAGGING_URL": "https://hooks.slack.com/services/id/id/id",
"FLAGGING_FOOTER_LINK": "https://habitrpg.github.io/flag-o-rama/",
"SUBSCRIPTIONS_URL": "https://hooks.slack.com/services/id/id/id"
},
"ITUNES_SHARED_SECRET": "aaaabbbbccccddddeeeeffff00001111",
"EMAILS" : {
"COMMUNITY_MANAGER_EMAIL" : "admin@habitica.com",
"TECH_ASSISTANCE_EMAIL" : "admin@habitica.com",
"PRESS_ENQUIRY_EMAIL" : "admin@habitica.com"
},
"LOGGLY" : {
"TOKEN" : "example-token",
"SUBDOMAIN" : "exmaple-subdomain"
},
"KAFKA": {
"GROUP_ID": "",
"CLOUDKARAFKA_BROKERS": "",
"CLOUDKARAFKA_USERNAME": "",
"CLOUDKARAFKA_PASSWORD": "",
"CLOUDKARAFKA_TOPIC_PREFIX": ""
},
"MIGRATION_CONNECT_STRING": "mongodb://localhost:27017/habitrpg?auto_reconnect=true"
"ADMIN_EMAIL": "you@example.com",
"AMAZON_PAYMENTS_CLIENT_ID": "CLIENT_ID",
"AMAZON_PAYMENTS_MODE": "sandbox",
"AMAZON_PAYMENTS_MWS_KEY": "MWS_KEY",
"AMAZON_PAYMENTS_MWS_SECRET": "MWS_SECRET",
"AMAZON_PAYMENTS_SELLER_ID": "SELLER_ID",
"AMPLITUDE_KEY": "AMPLITUDE_KEY",
"AMPLITUDE_SECRET": "AMPLITUDE_SECRET",
"BASE_URL": "http://localhost:3000",
"CRON_SAFE_MODE": "false",
"CRON_SEMI_SAFE_MODE": "false",
"DISABLE_REQUEST_LOGGING": "true",
"EMAILS_COMMUNITY_MANAGER_EMAIL": "admin@habitica.com",
"EMAILS_PRESS_ENQUIRY_EMAIL": "admin@habitica.com",
"EMAILS_TECH_ASSISTANCE_EMAIL": "admin@habitica.com",
"EMAIL_SERVER_AUTH_PASSWORD": "password",
"EMAIL_SERVER_AUTH_USER": "user",
"EMAIL_SERVER_URL": "http://example.com",
"ENABLE_CONSOLE_LOGS_IN_PROD": "false",
"ENABLE_CONSOLE_LOGS_IN_TEST": "false",
"FACEBOOK_KEY": "123456789012345",
"FACEBOOK_SECRET": "aaaabbbbccccddddeeeeffff00001111",
"FLAG_REPORT_EMAIL": "email@example.com, email2@example.com",
"GA_ID": "GA_ID",
"GOOGLE_CLIENT_ID": "123456789012345",
"GOOGLE_CLIENT_SECRET": "aaaabbbbccccddddeeeeffff00001111",
"IAP_GOOGLE_KEYDIR": "/path/to/google/public/key/dir/",
"IGNORE_REDIRECT": "true",
"ITUNES_SHARED_SECRET": "aaaabbbbccccddddeeeeffff00001111",
"LOGGLY_CLIENT_TOKEN": "token",
"LOGGLY_SUBDOMAIN": "example-subdomain",
"LOGGLY_TOKEN": "example-token",
"MAINTENANCE_MODE": "false",
"NODE_DB_URI": "mongodb://localhost/habitrpg",
"NODE_ENV": "development",
"PATH": "bin:node_modules/.bin:/usr/local/bin:/usr/bin:/bin",
"PAYPAL_BILLING_PLANS_basic_12mo": "basic_12mo",
"PAYPAL_BILLING_PLANS_basic_3mo": "basic_3mo",
"PAYPAL_BILLING_PLANS_basic_6mo": "basic_6mo",
"PAYPAL_BILLING_PLANS_basic_earned": "basic_earned",
"PAYPAL_BILLING_PLANS_google_6mo": "google_6mo",
"PAYPAL_CLIENT_ID": "client_id",
"PAYPAL_CLIENT_SECRET": "client_secret",
"PAYPAL_EXPERIENCE_PROFILE_ID": "xp_profile_id",
"PAYPAL_MODE": "sandbox",
"PLAY_API_ACCESS_TOKEN": "aaaabbbbccccddddeeeeffff00001111",
"PLAY_API_CLIENT_ID": "aaaabbbbccccddddeeeeffff00001111",
"PLAY_API_CLIENT_SECRET": "aaaabbbbccccddddeeeeffff00001111",
"PLAY_API_REFRESH_TOKEN": "aaaabbbbccccddddeeeeffff00001111",
"PORT": 3000,
"PUSH_CONFIGS_APN_ENABLED": "false",
"PUSH_CONFIGS_APN_KEY": "xxxxxxxxxx",
"PUSH_CONFIGS_APN_KEY_ID": "xxxxxxxxxx",
"PUSH_CONFIGS_APN_TEAM_ID": "aaabbbcccd",
"PUSH_CONFIGS_FCM_SERVER_API_KEY": "aaabbbcccd",
"S3_ACCESS_KEY_ID": "accessKeyId",
"S3_BUCKET": "bucket",
"S3_SECRET_ACCESS_KEY": "secretAccessKey",
"SESSION_SECRET": "YOUR SECRET HERE",
"SESSION_SECRET_IV": "12345678912345678912345678912345",
"SESSION_SECRET_KEY": "1234567891234567891234567891234567891234567891234567891234567891",
"SITE_HTTP_AUTH_ENABLED": "false",
"SITE_HTTP_AUTH_PASSWORD": "password",
"SITE_HTTP_AUTH_USERNAME": "admin",
"SLACK_FLAGGING_FOOTER_LINK": "https://habitrpg.github.io/flag-o-rama/",
"SLACK_FLAGGING_URL": "https://hooks.slack.com/services/id/id/id",
"SLACK_SUBSCRIPTIONS_URL": "https://hooks.slack.com/services/id/id/id",
"SLACK_URL": "https://hooks.slack.com/services/some-url",
"SMTP_HOST": "example.com",
"SMTP_PASS": "password",
"SMTP_PORT": 587,
"SMTP_SERVICE": "Gmail",
"SMTP_TLS": "true",
"SMTP_USER": "user@example.com",
"STRIPE_API_KEY": "aaaabbbbccccddddeeeeffff00001111",
"STRIPE_PUB_KEY": "22223333444455556666777788889999",
"TEST_DB_URI": "mongodb://localhost/habitrpg_test",
"TRANSIFEX_SLACK_CHANNEL": "transifex",
"WEB_CONCURRENCY": 1
}

View File

@@ -0,0 +1,48 @@
import monk from 'monk';
import nconf from 'nconf';
/*
* Output data on users who completed all the To-Do tasks in the 2018 Back-to-School Challenge.
* User ID,Profile Name
*/
const CONNECTION_STRING = nconf.get('MIGRATION_CONNECT_STRING');
const CHALLENGE_ID = '0acb1d56-1660-41a4-af80-9259f080b62b';
let dbUsers = monk(CONNECTION_STRING).get('users', { castIds: false });
let dbTasks = monk(CONNECTION_STRING).get('tasks', { castIds: false });
function usersReport() {
console.info('User ID,Profile Name');
let userCount = 0;
dbUsers.find(
{challenges: CHALLENGE_ID},
{fields:
{_id: 1, 'profile.name': 1}
},
).each((user, {close, pause, resume}) => {
pause();
userCount++;
let completedTodos = 0;
return dbTasks.find(
{
userId: user._id,
'challenge.id': CHALLENGE_ID,
type: 'todo',
},
{fields: {completed: 1}}
).each((task) => {
if (task.completed) completedTodos++;
}).then(() => {
if (completedTodos >= 7) {
console.info(`${user._id},${user.profile.name}`);
}
resume();
});
}).then(() => {
console.info(`${userCount} users reviewed`);
return process.exit(0);
});
}
module.exports = usersReport;

View File

@@ -167,7 +167,7 @@ gulp.task('test:content:safe', gulp.series('test:prepare:build', (cb) => {
gulp.task('test:api:unit', (done) => {
let runner = exec(
testBin('node_modules/.bin/istanbul cover --dir coverage/api-unit node_modules/mocha/bin/_mocha -- test/api/unit --recursive --require ./test/helpers/start-server'),
testBin('istanbul cover --dir coverage/api-unit node_modules/mocha/bin/_mocha -- test/api/unit --recursive --require ./test/helpers/start-server'),
(err) => {
if (err) {
process.exit(1);
@@ -180,12 +180,12 @@ gulp.task('test:api:unit', (done) => {
});
gulp.task('test:api:unit:watch', () => {
return gulp.watch(['website/server/libs/*', 'test/api/v3/unit/**/*', 'website/server/controllers/**/*'], gulp.series('test:api:unit', done => done()));
return gulp.watch(['website/server/libs/*', 'test/api/unit/**/*', 'website/server/controllers/**/*'], gulp.series('test:api:unit', done => done()));
});
gulp.task('test:api-v3:integration', (done) => {
let runner = exec(
testBin('node_modules/.bin/istanbul cover --dir coverage/api-v3-integration --report lcovonly node_modules/mocha/bin/_mocha -- test/api/v3/integration --recursive --require ./test/helpers/start-server'),
testBin('istanbul cover --dir coverage/api-v3-integration --report lcovonly node_modules/mocha/bin/_mocha -- test/api/v3/integration --recursive --require ./test/helpers/start-server'),
{maxBuffer: 500 * 1024},
(err) => {
if (err) {
@@ -217,7 +217,7 @@ gulp.task('test:api-v3:integration:separate-server', (done) => {
gulp.task('test:api-v4:integration', (done) => {
let runner = exec(
testBin('node_modules/.bin/istanbul cover --dir coverage/api-v4-integration --report lcovonly node_modules/mocha/bin/_mocha -- test/api/v4 --recursive --require ./test/helpers/start-server'),
testBin('istanbul cover --dir coverage/api-v4-integration --report lcovonly node_modules/mocha/bin/_mocha -- test/api/v4 --recursive --require ./test/helpers/start-server'),
{maxBuffer: 500 * 1024},
(err) => {
if (err) {
@@ -254,4 +254,4 @@ gulp.task('test:api-v3', gulp.series(
'test:api:unit',
'test:api-v3:integration',
done => done()
));
));

View File

@@ -8,6 +8,7 @@ const authorUuid = 'ed4c688c-6652-4a92-9d03-a5a79844174a'; // ... own data is do
const monk = require('monk');
const nconf = require('nconf');
const uuid = require('uuid').v4;
const Inbox = require('../website/server/models/message').inboxModel;
const connectionString = nconf.get('MIGRATION_CONNECT_STRING'); // FOR TEST DATABASE
@@ -84,16 +85,39 @@ function updateUser (user) {
return newMsg.toJSON();
});
return dbInboxes.insert(newInboxMessages)
.then(() => {
const promises = newInboxMessages.map(newMsg => {
return (async function fn () {
const existing = await dbInboxes.find({_id: newMsg._id});
if (existing.length > 0) {
if (
existing[0].ownerId === newMsg.ownerId &&
existing[0].text === newMsg.text &&
existing[0].uuid === newMsg.uuid &&
existing[0].sent === newMsg.sent
) {
return null;
}
newMsg.id = newMsg._id = uuid();
}
return newMsg;
})();
});
return Promise.all(promises)
.then((filteredNewMsg) => {
filteredNewMsg = filteredNewMsg.filter(m => Boolean(m && m.id && m._id && m.id == m._id));
return dbInboxes.insert(filteredNewMsg);
}).then(() => {
return dbUsers.update({_id: user._id}, {
$set: {
migration: migrationName,
'inbox.messages': {},
},
});
})
.catch((err) => {
}).catch((err) => {
console.log(err);
return exiting(1, `ERROR! ${ err}`);
});

View File

@@ -0,0 +1,107 @@
const MIGRATION_NAME = '20181003_username_email.js';
let authorName = 'Sabe'; // in case script author needs to know when their ...
let authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; // ... own data is done
/*
* Send emails to eligible users announcing upcoming username changes
*/
import monk from 'monk';
import nconf from 'nconf';
import { sendTxn } from '../../website/server/libs/email';
const CONNECTION_STRING = nconf.get('MIGRATION_CONNECT_STRING');
let dbUsers = monk(CONNECTION_STRING).get('users', { castIds: false });
function processUsers (lastId) {
// specify a query to limit the affected users (empty for all users):
let query = {
migration: {$ne: MIGRATION_NAME},
'auth.timestamps.loggedin': {$gt: new Date('2018-04-01')},
};
if (lastId) {
query._id = {
$gt: lastId,
};
}
dbUsers.find(query, {
sort: {_id: 1},
limit: 100,
fields: [
'_id',
'auth',
'preferences',
'profile',
], // specify fields we are interested in to limit retrieved data (empty if we're not reading data):
})
.then(updateUsers)
.catch((err) => {
console.log(err);
return exiting(1, `ERROR! ${ err}`);
});
}
let progressCount = 1000;
let count = 0;
function updateUsers (users) {
if (!users || users.length === 0) {
console.warn('All appropriate users found and modified.');
displayData();
return;
}
let userPromises = users.map(updateUser);
let lastUser = users[users.length - 1];
return Promise.all(userPromises)
.then(() => delay(7000))
.then(() => {
processUsers(lastUser._id);
});
}
function updateUser (user) {
count++;
dbUsers.update({_id: user._id}, {$set: {migration: MIGRATION_NAME}});
sendTxn(
user,
'username-change',
[{name: 'UNSUB_EMAIL_TYPE_URL', content: '/user/settings/notifications?unsubFrom=importantAnnouncements'},
{name: 'LOGIN_NAME', content: user.auth.local.username}]
);
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
if (user._id === authorUuid) console.warn(`${authorName} processed`);
}
function displayData () {
console.warn(`\n${count} users processed\n`);
return exiting(0);
}
function delay (t, v) {
return new Promise(function batchPause (resolve) {
setTimeout(resolve.bind(null, v), t);
});
}
function exiting (code, msg) {
code = code || 0; // 0 = success
if (code && !msg) {
msg = 'ERROR!';
}
if (msg) {
if (code) {
console.error(msg);
} else {
console.log(msg);
}
}
process.exit(code);
}
module.exports = processUsers;

View File

@@ -0,0 +1,66 @@
/* eslint-disable no-console */
const MIGRATION_NAME = '20181023_veteran_pet_ladder';
import { model as User } from '../../website/server/models/user';
const progressCount = 1000;
let count = 0;
async function updateUser (user) {
count++;
const set = {};
set.migration = MIGRATION_NAME;
if (user.items.pets['Bear-Veteran']) {
set['items.pets.Fox-Veteran'] = 5;
} else if (user.items.pets['Lion-Veteran']) {
set['items.pets.Bear-Veteran'] = 5;
} else if (user.items.pets['Tiger-Veteran']) {
set['items.pets.Lion-Veteran'] = 5;
} else if (user.items.pets['Wolf-Veteran']) {
set['items.pets.Tiger-Veteran'] = 5;
} else {
set['items.pets.Wolf-Veteran'] = 5;
}
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
return await User.update({_id: user._id}, {$set: set}).exec();
}
module.exports = async function processUsers () {
let query = {
migration: {$ne: MIGRATION_NAME},
'flags.verifiedUsername': true,
};
const fields = {
_id: 1,
items: 1,
migration: 1,
flags: 1,
};
while (true) { // eslint-disable-line no-constant-condition
const users = await User // eslint-disable-line no-await-in-loop
.find(query)
.limit(250)
.sort({_id: 1})
.select(fields)
.lean()
.exec();
if (users.length === 0) {
console.warn('All appropriate users found and modified.');
console.warn(`\n${count} users processed\n`);
break;
} else {
query._id = {
$gt: users[users.length - 1],
};
}
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
}
};

View File

@@ -0,0 +1,116 @@
/*
* Award Habitoween ladder items to participants in this month's Habitoween festivities
*/
import monk from 'monk';
import nconf from 'nconf';
const MIGRATION_NAME = '20181030_habitoween_ladder.js'; // Update when running in future years
const CONNECTION_STRING = nconf.get('MIGRATION_CONNECT_STRING');
const AUTHOR_NAME = 'Sabe'; // in case script author needs to know when their ...
const AUTHOR_UUID = '7f14ed62-5408-4e1b-be83-ada62d504931'; // ... own data is done
let dbUsers = monk(CONNECTION_STRING).get('users', { castIds: false });
function processUsers (lastId) {
// specify a query to limit the affected users (empty for all users):
let query = {
migration: {$ne: MIGRATION_NAME},
'auth.timestamps.loggedin': {$gt: new Date('2018-10-01')},
};
if (lastId) {
query._id = {
$gt: lastId,
};
}
dbUsers.find(query, {
sort: {_id: 1},
limit: 250,
fields: [
'items.mounts',
'items.pets',
], // specify fields we are interested in to limit retrieved data (empty if we're not reading data):
})
.then(updateUsers)
.catch((err) => {
console.log(err);
return exiting(1, `ERROR! ${err}`);
});
}
const PROGRESS_COUNT = 1000;
let count = 0;
function updateUsers (users) {
if (!users || users.length === 0) {
console.warn('All appropriate users found and modified.');
displayData();
return;
}
let userPromises = users.map(updateUser);
let lastUser = users[users.length - 1];
return Promise.all(userPromises)
.then(() => {
processUsers(lastUser._id);
});
}
function updateUser (user) {
count++;
let set = {};
let inc = {
'items.food.Candy_Skeleton': 1,
'items.food.Candy_Base': 1,
'items.food.Candy_CottonCandyBlue': 1,
'items.food.Candy_CottonCandyPink': 1,
'items.food.Candy_Shade': 1,
'items.food.Candy_White': 1,
'items.food.Candy_Golden': 1,
'items.food.Candy_Zombie': 1,
'items.food.Candy_Desert': 1,
'items.food.Candy_Red': 1,
};
if (user && user.items && user.items.pets && user.items.mounts['JackOLantern-Ghost']) {
set['items.pets.JackOLantern-Glow'] = 5;
} else if (user && user.items && user.items.pets && user.items.pets['JackOLantern-Ghost']) {
set['items.mounts.JackOLantern-Ghost'] = true;
} else if (user && user.items && user.items.mounts && user.items.mounts['JackOLantern-Base']) {
set['items.pets.JackOLantern-Ghost'] = 5;
} else if (user && user.items && user.items.pets && user.items.pets['JackOLantern-Base']) {
set['items.mounts.JackOLantern-Base'] = true;
} else {
set['items.pets.JackOLantern-Base'] = 5;
}
dbUsers.update({_id: user._id}, {$set: set, $inc: inc});
if (count % PROGRESS_COUNT === 0) console.warn(`${count} ${user._id}`);
if (user._id === AUTHOR_UUID) console.warn(`${AUTHOR_NAME} processed`);
}
function displayData () {
console.warn(`\n${count} users processed\n`);
return exiting(0);
}
function exiting (code, msg) {
code = code || 0; // 0 = success
if (code && !msg) {
msg = 'ERROR!';
}
if (msg) {
if (code) {
console.error(msg);
} else {
console.log(msg);
}
}
process.exit(code);
}
module.exports = processUsers;

View File

@@ -0,0 +1,109 @@
const MIGRATION_NAME = '20181108_username_email.js';
const AUTHOR_NAME = 'Sabe'; // in case script author needs to know when their ...
const AUTHOR_UUID = '7f14ed62-5408-4e1b-be83-ada62d504931'; // ... own data is done
/*
* Send emails to eligible users announcing upcoming username changes
*/
import monk from 'monk';
import nconf from 'nconf';
import { sendTxn } from '../../../website/server/libs/email';
const CONNECTION_STRING = nconf.get('MIGRATION_CONNECT_STRING');
const BASE_URL = nconf.get('BASE_URL');
let dbUsers = monk(CONNECTION_STRING).get('users', { castIds: false });
function processUsers (lastId) {
// specify a query to limit the affected users (empty for all users):
let query = {
migration: {$ne: MIGRATION_NAME},
'flags.verifiedUsername': {$ne: true},
'auth.timestamps.loggedin': {$gt: new Date('2018-10-25')},
};
if (lastId) {
query._id = {
$gt: lastId,
};
}
dbUsers.find(query, {
sort: {_id: 1},
limit: 100,
fields: [
'_id',
'auth',
'preferences',
'profile',
], // specify fields we are interested in to limit retrieved data (empty if we're not reading data):
})
.then(updateUsers)
.catch((err) => {
console.log(err);
return exiting(1, `ERROR! ${ err}`);
});
}
let progressCount = 1000;
let count = 0;
function updateUsers (users) {
if (!users || users.length === 0) {
console.warn('All appropriate users found and modified.');
displayData();
return;
}
let userPromises = users.map(updateUser);
let lastUser = users[users.length - 1];
return Promise.all(userPromises)
.then(() => delay(7000))
.then(() => {
processUsers(lastUser._id);
});
}
function updateUser (user) {
count++;
dbUsers.update({_id: user._id}, {$set: {migration: MIGRATION_NAME}});
sendTxn(
user,
'username-change-follow-up',
[{name: 'LOGIN_NAME', content: user.auth.local.username},
{name: 'BASE_URL', content: BASE_URL}]
);
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
if (user._id === AUTHOR_UUID) console.warn(`${AUTHOR_NAME} processed`);
}
function displayData () {
console.warn(`\n${count} users processed\n`);
return exiting(0);
}
function delay (t, v) {
return new Promise(function batchPause (resolve) {
setTimeout(resolve.bind(null, v), t);
});
}
function exiting (code, msg) {
code = code || 0; // 0 = success
if (code && !msg) {
msg = 'ERROR!';
}
if (msg) {
if (code) {
console.error(msg);
} else {
console.log(msg);
}
}
process.exit(code);
}
module.exports = processUsers;

View File

@@ -0,0 +1,109 @@
/* eslint-disable no-console */
const MIGRATION_NAME = '20181122_turkey_day';
import mongoose from 'mongoose';
import { model as User } from '../../website/server/models/user';
const progressCount = 1000;
let count = 0;
async function updateUser (user) {
count++;
const set = {};
let push;
set.migration = MIGRATION_NAME;
if (typeof user.items.gear.owned.armor_special_turkeyArmorBase !== 'undefined') {
set['items.gear.owned.head_special_turkeyHelmGilded'] = false;
set['items.gear.owned.armor_special_turkeyArmorGilded'] = false;
set['items.gear.owned.back_special_turkeyTailGilded'] = false;
push = [
{
type: 'marketGear',
path: 'gear.flat.head_special_turkeyHelmGilded',
_id: new mongoose.Types.ObjectId(),
},
{
type: 'marketGear',
path: 'gear.flat.armor_special_turkeyArmorGilded',
_id: new mongoose.Types.ObjectId(),
},
{
type: 'marketGear',
path: 'gear.flat.back_special_turkeyTailGilded',
_id: new mongoose.Types.ObjectId(),
},
];
} else if (user.items && user.items.mounts && user.items.mounts['Turkey-Gilded']) {
set['items.gear.owned.head_special_turkeyHelmBase'] = false;
set['items.gear.owned.armor_special_turkeyArmorBase'] = false;
set['items.gear.owned.back_special_turkeyTailBase'] = false;
push = [
{
type: 'marketGear',
path: 'gear.flat.head_special_turkeyHelmBase',
_id: new mongoose.Types.ObjectId(),
},
{
type: 'marketGear',
path: 'gear.flat.armor_special_turkeyArmorBase',
_id: new mongoose.Types.ObjectId(),
},
{
type: 'marketGear',
path: 'gear.flat.back_special_turkeyTailBase',
_id: new mongoose.Types.ObjectId(),
},
];
} else if (user.items && user.items.pets && user.items.pets['Turkey-Gilded']) {
set['items.mounts.Turkey-Gilded'] = true;
} else if (user.items && user.items.mounts && user.items.mounts['Turkey-Base']) {
set['items.pets.Turkey-Gilded'] = 5;
} else if (user.items && user.items.pets && user.items.pets['Turkey-Base']) {
set['items.mounts.Turkey-Base'] = true;
} else {
set['items.pets.Turkey-Base'] = 5;
}
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
if (push) {
return await User.update({_id: user._id}, {$set: set, $push: {pinnedItems: {$each: push}}}).exec();
} else {
return await User.update({_id: user._id}, {$set: set}).exec();
}
}
module.exports = async function processUsers () {
let query = {
migration: {$ne: MIGRATION_NAME},
};
const fields = {
_id: 1,
items: 1,
};
while (true) { // eslint-disable-line no-constant-condition
const users = await User // eslint-disable-line no-await-in-loop
.find(query)
.limit(250)
.sort({_id: 1})
.select(fields)
.lean()
.exec();
if (users.length === 0) {
console.warn('All appropriate users found and modified.');
console.warn(`\n${count} users processed\n`);
break;
} else {
query._id = {
$gt: users[users.length - 1],
};
}
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
}
};

View File

@@ -0,0 +1,110 @@
/* eslint-disable no-console */
const MIGRATION_NAME = '20181231_nye';
import { model as User } from '../../../website/server/models/user';
import mongoose from 'mongoose';
import { v4 as uuid } from 'uuid';
const progressCount = 1000;
let count = 0;
async function updateUser (user) {
count++;
const set = {'flags.newStuff': true};
let push;
set.migration = MIGRATION_NAME;
if (typeof user.items.gear.owned.head_special_nye2017 !== 'undefined') {
set['items.gear.owned.head_special_nye2018'] = false;
push = [
{
type: 'marketGear',
path: 'gear.flat.head_special_nye2018',
_id: uuid(),
},
];
} else if (typeof user.items.gear.owned.head_special_nye2016 !== 'undefined') {
set['items.gear.owned.head_special_nye2017'] = false;
push = [
{
type: 'marketGear',
path: 'gear.flat.head_special_nye2017',
_id: uuid(),
},
];
} else if (typeof user.items.gear.owned.head_special_nye2015 !== 'undefined') {
set['items.gear.owned.head_special_nye2016'] = false;
push = [
{
type: 'marketGear',
path: 'gear.flat.head_special_nye2016',
_id: uuid(),
},
];
} else if (typeof user.items.gear.owned.head_special_nye2014 !== 'undefined') {
set['items.gear.owned.head_special_nye2015'] = false;
push = [
{
type: 'marketGear',
path: 'gear.flat.head_special_nye2015',
_id: uuid(),
},
];
} else if (typeof user.items.gear.owned.head_special_nye !== 'undefined') {
set['items.gear.owned.head_special_nye2014'] = false;
push = [
{
type: 'marketGear',
path: 'gear.flat.head_special_nye2014',
_id: uuid(),
},
];
} else {
set['items.gear.owned.head_special_nye'] = false;
push = [
{
type: 'marketGear',
path: 'gear.flat.head_special_nye',
_id: uuid(),
},
];
}
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
return await User.update({_id: user._id}, {$set: set, $push: {pinnedItems: {$each: push}}}).exec();
}
module.exports = async function processUsers () {
let query = {
migration: {$ne: MIGRATION_NAME},
};
const fields = {
_id: 1,
items: 1,
};
while (true) { // eslint-disable-line no-constant-condition
const users = await User // eslint-disable-line no-await-in-loop
.find(query)
.limit(250)
.sort({_id: 1})
.select(fields)
.lean()
.exec();
if (users.length === 0) {
console.warn('All appropriate users found and modified.');
console.warn(`\n${count} users processed\n`);
break;
} else {
query._id = {
$gt: users[users.length - 1],
};
}
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
}
};

View File

@@ -0,0 +1,110 @@
import monk from 'monk';
import nconf from 'nconf';
const migrationName = 'mystery-items-201808.js'; // Update per month
const authorName = 'Sabe'; // in case script author needs to know when their ...
const authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; // ... own data is done
/*
* Award this month's mystery items to subscribers
*/
const MYSTERY_ITEMS = ['armor_mystery_201810', 'head_mystery_201810'];
const CONNECTION_STRING = nconf.get('MIGRATION_CONNECT_STRING');
let dbUsers = monk(CONNECTION_STRING).get('users', { castIds: false });
let UserNotification = require('../../website/server/models/userNotification').model;
function processUsers (lastId) {
// specify a query to limit the affected users (empty for all users):
let query = {
migration: {$ne: migrationName},
'purchased.plan.customerId': { $ne: null },
$or: [
{ 'purchased.plan.dateTerminated': { $gte: new Date() } },
{ 'purchased.plan.dateTerminated': { $exists: false } },
{ 'purchased.plan.dateTerminated': { $eq: null } },
],
};
if (lastId) {
query._id = {
$gt: lastId,
};
}
dbUsers.find(query, {
sort: {_id: 1},
limit: 250,
fields: [
], // specify fields we are interested in to limit retrieved data (empty if we're not reading data):
})
.then(updateUsers)
.catch((err) => {
console.log(err);
return exiting(1, `ERROR! ${ err}`);
});
}
let progressCount = 1000;
let count = 0;
function updateUsers (users) {
if (!users || users.length === 0) {
console.warn('All appropriate users found and modified.');
displayData();
return;
}
let userPromises = users.map(updateUser);
let lastUser = users[users.length - 1];
return Promise.all(userPromises)
.then(() => {
processUsers(lastUser._id);
});
}
function updateUser (user) {
count++;
const addToSet = {
'purchased.plan.mysteryItems': {
$each: MYSTERY_ITEMS,
},
};
const push = {
notifications: (new UserNotification({
type: 'NEW_MYSTERY_ITEMS',
data: {
MYSTERY_ITEMS,
},
})).toJSON(),
};
dbUsers.update({_id: user._id}, {$addToSet: addToSet, $push: push});
if (count % progressCount === 0) console.warn(`${count } ${ user._id}`);
if (user._id === authorUuid) console.warn(`${authorName } processed`);
}
function displayData () {
console.warn(`\n${ count } users processed\n`);
return exiting(0);
}
function exiting (code, msg) {
code = code || 0; // 0 = success
if (code && !msg) {
msg = 'ERROR!';
}
if (msg) {
if (code) {
console.error(msg);
} else {
console.log(msg);
}
}
process.exit(code);
}
module.exports = processUsers;

View File

@@ -17,5 +17,12 @@ function setUpServer () {
setUpServer();
// Replace this with your migration
const processUsers = require('./users/takeThis.js');
processUsers();
const processUsers = require('./archive/2018/20181231_nye.js');
processUsers()
.then(function success () {
process.exit(0);
})
.catch(function failure (err) {
console.log(err);
process.exit(1);
});

View File

@@ -0,0 +1,61 @@
/* eslint-disable no-console */
import { sendTxn } from '../../../website/server/libs/email';
import { model as User } from '../../website/server/models/user';
import moment from 'moment';
import nconf from 'nconf';
const BASE_URL = nconf.get('BASE_URL');
const EMAIL_SLUG = 'mandrill-email-slug'; // Set email template to send
const MIGRATION_NAME = 'bulk-email';
const progressCount = 1000;
let count = 0;
async function updateUser (user) {
count++;
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
sendTxn(
user,
EMAIL_SLUG,
[{name: 'BASE_URL', content: BASE_URL}] // Add variables from template
);
return await User.update({_id: user._id}, {$set: {migration: MIGRATION_NAME}}).exec();
}
module.exports = async function processUsers () {
let query = {
migration: {$ne: MIGRATION_NAME},
'auth.timestamps.loggedin': {$gt: moment().subtract(2, 'weeks').toDate()}, // customize or remove to target different populations
};
const fields = {
_id: 1,
auth: 1,
preferences: 1,
profile: 1,
};
while (true) { // eslint-disable-line no-constant-condition
const users = await User // eslint-disable-line no-await-in-loop
.find(query)
.limit(250)
.sort({_id: 1})
.select(fields)
.lean()
.exec();
if (users.length === 0) {
console.warn('All appropriate users found and modified.');
console.warn(`\n${count} users processed\n`);
break;
} else {
query._id = {
$gt: users[users.length - 1],
};
}
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
}
};

View File

@@ -1,70 +1,13 @@
import monk from 'monk';
import nconf from 'nconf';
/* eslint-disable no-console */
const MIGRATION_NAME = 'mystery_items_201812';
const MYSTERY_ITEMS = ['headAccessory_mystery_201812', 'back_mystery_201812'];
import { model as User } from '../../website/server/models/user';
import { model as UserNotification } from '../../website/server/models/userNotification';
const migrationName = 'mystery-items-201808.js'; // Update per month
const authorName = 'Sabe'; // in case script author needs to know when their ...
const authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; // ... own data is done
/*
* Award this month's mystery items to subscribers
*/
const MYSTERY_ITEMS = ['armor_mystery_201809', 'head_mystery_201809'];
const CONNECTION_STRING = nconf.get('MIGRATION_CONNECT_STRING');
let dbUsers = monk(CONNECTION_STRING).get('users', { castIds: false });
let UserNotification = require('../../website/server/models/userNotification').model;
function processUsers (lastId) {
// specify a query to limit the affected users (empty for all users):
let query = {
migration: {$ne: migrationName},
'purchased.plan.customerId': { $ne: null },
$or: [
{ 'purchased.plan.dateTerminated': { $gte: new Date() } },
{ 'purchased.plan.dateTerminated': { $exists: false } },
{ 'purchased.plan.dateTerminated': { $eq: null } },
],
};
if (lastId) {
query._id = {
$gt: lastId,
};
}
dbUsers.find(query, {
sort: {_id: 1},
limit: 250,
fields: [
], // specify fields we are interested in to limit retrieved data (empty if we're not reading data):
})
.then(updateUsers)
.catch((err) => {
console.log(err);
return exiting(1, `ERROR! ${ err}`);
});
}
let progressCount = 1000;
const progressCount = 1000;
let count = 0;
function updateUsers (users) {
if (!users || users.length === 0) {
console.warn('All appropriate users found and modified.');
displayData();
return;
}
let userPromises = users.map(updateUser);
let lastUser = users[users.length - 1];
return Promise.all(userPromises)
.then(() => {
processUsers(lastUser._id);
});
}
function updateUser (user) {
async function updateUser (user) {
count++;
const addToSet = {
@@ -80,31 +23,49 @@ function updateUser (user) {
},
})).toJSON(),
};
const set = {
migration: MIGRATION_NAME,
};
dbUsers.update({_id: user._id}, {$addToSet: addToSet, $push: push});
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
if (count % progressCount === 0) console.warn(`${count } ${ user._id}`);
if (user._id === authorUuid) console.warn(`${authorName } processed`);
return await User.update({_id: user._id}, {$set: set, $push: push, $addToSet: addToSet}).exec();
}
function displayData () {
console.warn(`\n${ count } users processed\n`);
return exiting(0);
}
module.exports = async function processUsers () {
let query = {
migration: {$ne: MIGRATION_NAME},
'purchased.plan.customerId': { $ne: null },
$or: [
{ 'purchased.plan.dateTerminated': { $gte: new Date() } },
{ 'purchased.plan.dateTerminated': { $exists: false } },
{ 'purchased.plan.dateTerminated': { $eq: null } },
],
};
function exiting (code, msg) {
code = code || 0; // 0 = success
if (code && !msg) {
msg = 'ERROR!';
}
if (msg) {
if (code) {
console.error(msg);
} else {
console.log(msg);
const fields = {
_id: 1,
};
while (true) { // eslint-disable-line no-constant-condition
const users = await User // eslint-disable-line no-await-in-loop
.find(query)
.limit(250)
.sort({_id: 1})
.select(fields)
.lean()
.exec();
if (users.length === 0) {
console.warn('All appropriate users found and modified.');
console.warn(`\n${count} users processed\n`);
break;
} else {
query._id = {
$gt: users[users.length - 1],
};
}
}
process.exit(code);
}
module.exports = processUsers;
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
}
};

View File

@@ -0,0 +1,81 @@
/* eslint-disable no-console */
const MIGRATION_NAME = '20181203_take_this';
import { v4 as uuid } from 'uuid';
import { model as User } from '../../website/server/models/user';
const progressCount = 1000;
let count = 0;
async function updateUser (user) {
count++;
const set = {};
let push;
set.migration = MIGRATION_NAME;
if (typeof user.items.gear.owned.back_special_takeThis !== 'undefined') {
push = false;
} else if (typeof user.items.gear.owned.body_special_takeThis !== 'undefined') {
set['items.gear.owned.back_special_takeThis'] = false;
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.back_special_takeThis', _id: uuid()}};
} else if (typeof user.items.gear.owned.head_special_takeThis !== 'undefined') {
set['items.gear.owned.body_special_takeThis'] = false;
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.body_special_takeThis', _id: uuid()}};
} else if (typeof user.items.gear.owned.armor_special_takeThis !== 'undefined') {
set['items.gear.owned.head_special_takeThis'] = false;
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.head_special_takeThis', _id: uuid()}};
} else if (typeof user.items.gear.owned.weapon_special_takeThis !== 'undefined') {
set['items.gear.owned.armor_special_takeThis'] = false;
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.armor_special_takeThis', _id: uuid()}};
} else if (typeof user.items.gear.owned.shield_special_takeThis !== 'undefined') {
set['items.gear.owned.weapon_special_takeThis'] = false;
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.weapon_special_takeThis', _id: uuid()}};
} else {
set['items.gear.owned.shield_special_takeThis'] = false;
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.shield_special_takeThis', _id: uuid()}};
}
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
if (push) {
return await User.update({_id: user._id}, {$set: set, $push: push}).exec();
} else {
return await User.update({_id: user._id}, {$set: set}).exec();
}
}
module.exports = async function processUsers () {
let query = {
migration: {$ne: MIGRATION_NAME},
challenges: '00708425-d477-41a5-bf27-6270466e7976',
};
const fields = {
_id: 1,
items: 1,
};
while (true) { // eslint-disable-line no-constant-condition
const users = await User // eslint-disable-line no-await-in-loop
.find(query)
.limit(250)
.sort({_id: 1})
.select(fields)
.lean()
.exec();
if (users.length === 0) {
console.warn('All appropriate users found and modified.');
console.warn(`\n${count} users processed\n`);
break;
} else {
query._id = {
$gt: users[users.length - 1],
};
}
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
}
};

6558
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
{
"name": "habitica",
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
"version": "4.64.0",
"version": "4.78.0",
"main": "./website/server/index.js",
"dependencies": {
"@slack/client": "^3.8.1",
@@ -11,7 +11,7 @@
"apidoc": "^0.17.5",
"apn": "^2.2.0",
"autoprefixer": "^8.5.0",
"aws-sdk": "^2.239.1",
"aws-sdk": "^2.329.0",
"axios": "^0.18.0",
"axios-progress-bar": "^1.2.0",
"babel-core": "^6.26.3",
@@ -33,9 +33,9 @@
"compression": "^1.7.2",
"cookie-session": "^1.2.0",
"coupon-code": "^0.4.5",
"cross-env": "^5.1.5",
"cross-env": "^5.2.0",
"css-loader": "^0.28.11",
"csv-stringify": "^3.0.0",
"csv-stringify": "^4.3.1",
"cwait": "^1.1.1",
"domain-middleware": "~0.1.0",
"express": "^4.16.3",
@@ -46,29 +46,29 @@
"got": "^9.0.0",
"gulp": "^4.0.0",
"gulp-babel": "^7.0.1",
"gulp-imagemin": "^4.1.0",
"gulp-nodemon": "^2.2.1",
"gulp-imagemin": "^5.0.3",
"gulp-nodemon": "^2.4.1",
"gulp.spritesmith": "^6.9.0",
"habitica-markdown": "^1.3.0",
"hellojs": "^1.15.1",
"html-webpack-plugin": "^3.2.0",
"image-size": "^0.6.2",
"in-app-purchase": "^1.9.4",
"in-app-purchase": "^1.10.2",
"intro.js": "^2.9.3",
"jquery": ">=3.0.0",
"js2xmlparser": "^3.0.0",
"lodash": "^4.17.10",
"merge-stream": "^1.0.0",
"method-override": "^2.3.5",
"method-override": "^3.0.0",
"moment": "^2.22.1",
"moment-recur": "^1.0.7",
"mongoose": "^5.1.3",
"mongoose": "^5.3.4",
"morgan": "^1.7.0",
"nconf": "^0.10.0",
"node-gcm": "^0.14.4",
"node-gcm": "^1.0.2",
"node-sass": "^4.9.0",
"nodemailer": "^4.6.4",
"ora": "^2.1.0",
"ora": "^3.0.0",
"pageres": "^4.1.1",
"passport": "^0.4.0",
"passport-facebook": "^2.0.0",
@@ -79,19 +79,18 @@
"postcss-easy-import": "^3.0.0",
"ps-tree": "^1.0.0",
"pug": "^2.0.3",
"pusher": "^1.3.0",
"rimraf": "^2.4.3",
"sass-loader": "^7.0.0",
"sass-loader": "^7.0.3",
"shelljs": "^0.8.2",
"short-uuid": "^3.0.0",
"smartbanner.js": "^1.9.1",
"stripe": "^5.9.0",
"superagent": "^3.8.3",
"superagent": "^4.0.0",
"svg-inline-loader": "^0.8.0",
"svg-url-loader": "^2.3.2",
"svgo": "^1.0.5",
"svgo-loader": "^2.1.0",
"universal-analytics": "^0.4.16",
"universal-analytics": "^0.4.17",
"update": "^0.7.4",
"upgrade": "^1.1.0",
"url-loader": "^1.0.0",
@@ -108,15 +107,15 @@
"vuedraggable": "^2.15.0",
"vuejs-datepicker": "git://github.com/habitrpg/vuejs-datepicker.git#5d237615463a84a23dd6f3f77c6ab577d68593ec",
"webpack": "^3.12.0",
"webpack-merge": "^4.0.0",
"winston": "^2.4.2",
"webpack-merge": "^4.1.3",
"winston": "^2.4.3",
"winston-loggly-bulk": "^2.0.2",
"xml2js": "^0.4.4"
},
"private": true,
"engines": {
"node": "^8.9.4",
"npm": "^5.6.0"
"node": "^10",
"npm": "^6"
},
"scripts": {
"lint": "eslint --ext .js,.vue .",
@@ -145,13 +144,13 @@
"apidoc": "gulp apidoc"
},
"devDependencies": {
"@vue/test-utils": "^1.0.0-beta.16",
"@vue/test-utils": "^1.0.0-beta.19",
"babel-plugin-istanbul": "^4.1.6",
"babel-plugin-syntax-object-rest-spread": "^6.13.0",
"chai": "^4.1.2",
"chai-as-promised": "^7.1.1",
"chalk": "^2.4.1",
"chromedriver": "^2.38.3",
"chromedriver": "^2.40.0",
"connect-history-api-fallback": "^1.1.0",
"coveralls": "^3.0.1",
"cross-spawn": "^6.0.5",
@@ -163,9 +162,9 @@
"eslint-plugin-mocha": "^5.0.0",
"eventsource-polyfill": "^0.9.6",
"expect.js": "^0.3.1",
"http-proxy-middleware": "^0.18.0",
"http-proxy-middleware": "^0.19.0",
"istanbul": "^1.1.0-alpha.1",
"karma": "^3.0.0",
"karma": "^3.1.3",
"karma-babel-preprocessor": "^7.0.0",
"karma-chai-plugins": "^0.9.0",
"karma-chrome-launcher": "^2.2.0",
@@ -181,18 +180,15 @@
"mocha": "^5.1.1",
"monk": "^6.0.6",
"nightwatch": "^0.9.21",
"puppeteer": "^1.4.0",
"puppeteer": "^1.5.0",
"require-again": "^2.0.0",
"selenium-server": "^3.12.0",
"sinon": "^4.5.0",
"sinon": "^6.3.5",
"sinon-chai": "^3.0.0",
"sinon-stub-promise": "^4.0.0",
"webpack-bundle-analyzer": "^2.12.0",
"webpack-dev-middleware": "^2.0.5",
"webpack-hot-middleware": "^2.22.2"
},
"optionalDependencies": {
"memwatch-next": "^0.3.0",
"node-rdkafka": "^2.3.0"
}
"optionalDependencies": {}
}

View File

@@ -0,0 +1,89 @@
/* eslint-disable no-console */
import axios from 'axios';
import { model as User } from '../website/server/models/user';
import nconf from 'nconf';
const AMPLITUDE_KEY = nconf.get('AMPLITUDE_KEY');
const AMPLITUDE_SECRET = nconf.get('AMPLITUDE_SECRET');
const BASE_URL = nconf.get('BASE_URL');
async function _deleteAmplitudeData (userId, email) {
const response = await axios.post(
'https://amplitude.com/api/2/deletions/users',
{
user_ids: userId, // eslint-disable-line camelcase
requester: email,
},
{
auth: {
username: AMPLITUDE_KEY,
password: AMPLITUDE_SECRET,
},
}
).catch((err) => {
console.log(err.response.data);
});
if (response) console.log(`${response.status} ${response.statusText}`);
}
async function _deleteHabiticaData (user, email) {
await User.update(
{_id: user._id},
{$set: {
'auth.local.email': email,
'auth.local.hashed_password': '$2a$10$QDnNh1j1yMPnTXDEOV38xOePEWFd4X8DSYwAM8XTmqmacG5X0DKjW',
'auth.local.passwordHashMethod': 'bcrypt',
}}
);
const response = await axios.delete(
`${BASE_URL}/api/v3/user`,
{
data: {
password: 'test',
},
headers: {
'x-api-user': user._id,
'x-api-key': user.apiToken,
},
}
).catch((err) => {
console.log(err.response.data);
});
if (response) {
console.log(`${response.status} ${response.statusText}`);
if (response.status === 200) console.log(`${user._id} removed. Last login: ${user.auth.timestamps.loggedin}`);
}
}
async function _processEmailAddress (email) {
const emailRegex = new RegExp(`^${email}`, 'i');
const users = await User.find({
$or: [
{'auth.local.email': emailRegex},
{'auth.facebook.emails.value': emailRegex},
{'auth.google.emails.value': emailRegex},
]},
{
_id: 1,
apiToken: 1,
auth: 1,
}).exec();
if (users.length < 1) {
console.log(`No users found with email address ${email}`);
} else {
for (const user of users) {
await _deleteAmplitudeData(user._id, email); // eslint-disable-line no-await-in-loop
await _deleteHabiticaData(user, email); // eslint-disable-line no-await-in-loop
}
}
}
function deleteUserData (emails) {
const emailPromises = emails.map(_processEmailAddress);
return Promise.all(emailPromises);
}
module.exports = deleteUserData;

View File

@@ -12,28 +12,27 @@ const nconf = require('nconf');
const _ = require('lodash');
const paypal = require('paypal-rest-sdk');
const blocks = require('../website/common').content.subscriptionBlocks;
const live = nconf.get('PAYPAL:mode') === 'live';
const live = nconf.get('PAYPAL_MODE') === 'live';
nconf.argv().env().file('user', path.join(path.resolve(__dirname, '../config.json')));
let OP = 'create'; // list create update remove
let OP = 'create'; // list get update create create-webprofile
paypal.configure({
mode: nconf.get('PAYPAL:mode'), // sandbox or live
client_id: nconf.get('PAYPAL:client_id'),
client_secret: nconf.get('PAYPAL:client_secret'),
mode: nconf.get('PAYPAL_MODE'), // sandbox or live
client_id: nconf.get('PAYPAL_CLIENT_ID'),
client_secret: nconf.get('PAYPAL_CLIENT_SECRET'),
});
// https://developer.paypal.com/docs/api/#billing-plans-and-agreements
let billingPlanTitle = 'Habitica Subscription';
let billingPlanAttributes = {
name: billingPlanTitle,
description: billingPlanTitle,
type: 'INFINITE',
merchant_preferences: {
auto_bill_amount: 'yes',
cancel_url: live ? 'https://habitica.com' : 'http://localhost:3000',
return_url: `${live ? 'https://habitica.com' : 'http://localhost:3000' }/paypal/subscribe/success`,
return_url: `${live ? 'https://habitica.com' : 'http://localhost:3000'}/paypal/subscribe/success`,
},
payment_definitions: [{
type: 'REGULAR',
@@ -45,7 +44,7 @@ let billingPlanAttributes = {
_.each(blocks, (block) => {
block.definition = _.cloneDeep(billingPlanAttributes);
_.merge(block.definition.payment_definitions[0], {
name: `${billingPlanTitle } ($${block.price} every ${block.months} months, recurring)`,
name: `${billingPlanTitle} ($${block.price} every ${block.months} months, recurring)`,
frequency_interval: `${block.months}`,
amount: {
currency: 'USD',
@@ -63,7 +62,7 @@ switch (OP) {
});
break;
case 'get':
paypal.billingPlan.get(nconf.get('PAYPAL:billing_plans:12'), (err, plan) => {
paypal.billingPlan.get(nconf.get('PAYPAL_BILLING_PLANS_basic_12mo'), (err, plan) => {
console.log({err, plan});
});
break;
@@ -75,7 +74,7 @@ switch (OP) {
cancel_url: 'https://habitica.com',
},
};
paypal.billingPlan.update(nconf.get('PAYPAL:billing_plans:12'), updatePayload, (err, res) => {
paypal.billingPlan.update(nconf.get('PAYPAL_BILLING_PLANS_basic_12mo'), updatePayload, (err, res) => {
console.log({err, plan: res});
});
break;
@@ -101,9 +100,6 @@ switch (OP) {
});
});
break;
case 'remove': break;
case 'create-webprofile':
let webexpinfo = {
name: 'HabiticaProfile',
@@ -116,4 +112,4 @@ switch (OP) {
console.log(error, result);
});
break;
}
}

View File

@@ -5,10 +5,17 @@ describe('Base model plugin', () => {
let schema;
beforeEach(() => {
schema = new mongoose.Schema();
schema = new mongoose.Schema({}, {
typeKey: '$type',
});
sandbox.stub(schema, 'add');
});
it('throws if "typeKey" is not set to $type', () => {
const schemaWithoutTypeKey = new mongoose.Schema();
expect(() => schemaWithoutTypeKey.plugin(baseModel)).to.throw;
});
it('adds a _id field to the schema', () => {
schema.plugin(baseModel);

View File

@@ -245,7 +245,9 @@ describe('Password Utilities', () => {
it('returns false if the user has no local auth', async () => {
let user = await generateUser({
auth: 'not an object with valid fields',
auth: {
facebook: {},
},
});
let res = await validatePasswordResetCodeAndFindUser(encrypt(JSON.stringify({
userId: user._id,

View File

@@ -48,7 +48,6 @@ describe('Amazon Payments - Cancel Subscription', () => {
function expectBillingAggreementDetailSpy () {
getBillingAgreementDetailsSpy = sinon.stub(amzLib, 'getBillingAgreementDetails')
.returnsPromise()
.resolves({
BillingAgreementDetails: {
BillingAgreementStatus: {State: 'Open'},
@@ -80,14 +79,14 @@ describe('Amazon Payments - Cancel Subscription', () => {
headers = {};
getBillingAgreementDetailsSpy = sinon.stub(amzLib, 'getBillingAgreementDetails');
getBillingAgreementDetailsSpy.returnsPromise().resolves({
getBillingAgreementDetailsSpy.resolves({
BillingAgreementDetails: {
BillingAgreementStatus: {State: 'Closed'},
},
});
paymentCancelSubscriptionSpy = sinon.stub(payments, 'cancelSubscription');
paymentCancelSubscriptionSpy.returnsPromise().resolves({});
paymentCancelSubscriptionSpy.resolves({});
});
afterEach(function () {
@@ -118,7 +117,7 @@ describe('Amazon Payments - Cancel Subscription', () => {
it('should close a user subscription if amazon not closed', async () => {
amzLib.getBillingAgreementDetails.restore();
expectBillingAggreementDetailSpy();
let closeBillingAgreementSpy = sinon.stub(amzLib, 'closeBillingAgreement').returnsPromise().resolves({});
let closeBillingAgreementSpy = sinon.stub(amzLib, 'closeBillingAgreement').resolves({});
billingAgreementId = user.purchased.plan.customerId;
await amzLib.cancelSubscription({user, headers});
@@ -164,7 +163,7 @@ describe('Amazon Payments - Cancel Subscription', () => {
it('should close a group subscription if amazon not closed', async () => {
amzLib.getBillingAgreementDetails.restore();
expectBillingAggreementDetailSpy();
let closeBillingAgreementSpy = sinon.stub(amzLib, 'closeBillingAgreement').returnsPromise().resolves({});
let closeBillingAgreementSpy = sinon.stub(amzLib, 'closeBillingAgreement').resolves({});
billingAgreementId = group.purchased.plan.customerId;
await amzLib.cancelSubscription({user, groupId: group._id, headers});

View File

@@ -68,22 +68,22 @@ describe('Amazon Payments - Checkout', () => {
orderReferenceId = 'orderReferenceId';
setOrderReferenceDetailsSpy = sinon.stub(amzLib, 'setOrderReferenceDetails');
setOrderReferenceDetailsSpy.returnsPromise().resolves({});
setOrderReferenceDetailsSpy.resolves({});
confirmOrderReferenceSpy = sinon.stub(amzLib, 'confirmOrderReference');
confirmOrderReferenceSpy.returnsPromise().resolves({});
confirmOrderReferenceSpy.resolves({});
authorizeSpy = sinon.stub(amzLib, 'authorize');
authorizeSpy.returnsPromise().resolves({});
authorizeSpy.resolves({});
closeOrderReferenceSpy = sinon.stub(amzLib, 'closeOrderReference');
closeOrderReferenceSpy.returnsPromise().resolves({});
closeOrderReferenceSpy.resolves({});
paymentBuyGemsStub = sinon.stub(payments, 'buyGems');
paymentBuyGemsStub.returnsPromise().resolves({});
paymentBuyGemsStub.resolves({});
paymentCreateSubscritionStub = sinon.stub(payments, 'createSubscription');
paymentCreateSubscritionStub.returnsPromise().resolves({});
paymentCreateSubscritionStub.resolves({});
sinon.stub(common, 'uuid').returns('uuid-generated');
});
@@ -111,7 +111,7 @@ describe('Amazon Payments - Checkout', () => {
}
it('should purchase gems', async () => {
sinon.stub(user, 'canGetGems').returnsPromise().resolves(true);
sinon.stub(user, 'canGetGems').resolves(true);
await amzLib.checkout({user, orderReferenceId, headers});
expectBuyGemsStub(amzLib.constants.PAYMENT_METHOD);
@@ -140,7 +140,7 @@ describe('Amazon Payments - Checkout', () => {
});
it('should error if user cannot get gems gems', async () => {
sinon.stub(user, 'canGetGems').returnsPromise().resolves(false);
sinon.stub(user, 'canGetGems').resolves(false);
await expect(amzLib.checkout({user, orderReferenceId, headers})).to.eventually.be.rejected.and.to.eql({
httpCode: 401,
message: i18n.t('groupPolicyCannotGetGems'),

View File

@@ -46,16 +46,16 @@ describe('Amazon Payments - Subscribe', () => {
headers = {};
amazonSetBillingAgreementDetailsSpy = sinon.stub(amzLib, 'setBillingAgreementDetails');
amazonSetBillingAgreementDetailsSpy.returnsPromise().resolves({});
amazonSetBillingAgreementDetailsSpy.resolves({});
amazonConfirmBillingAgreementSpy = sinon.stub(amzLib, 'confirmBillingAgreement');
amazonConfirmBillingAgreementSpy.returnsPromise().resolves({});
amazonConfirmBillingAgreementSpy.resolves({});
amazonAuthorizeOnBillingAgreementSpy = sinon.stub(amzLib, 'authorizeOnBillingAgreement');
amazonAuthorizeOnBillingAgreementSpy.returnsPromise().resolves({});
amazonAuthorizeOnBillingAgreementSpy.resolves({});
createSubSpy = sinon.stub(payments, 'createSubscription');
createSubSpy.returnsPromise().resolves({});
createSubSpy.resolves({});
sinon.stub(common, 'uuid').returns('uuid-generated');
});

View File

@@ -37,7 +37,7 @@ describe('#upgradeGroupPlan', () => {
await group.save();
spy = sinon.stub(amzLib, 'authorizeOnBillingAgreement');
spy.returnsPromise().resolves([]);
spy.resolves([]);
uuidString = 'uuid-v4';
sinon.stub(uuid, 'v4').returns(uuidString);

View File

@@ -24,16 +24,16 @@ describe('Apple Payments', () => {
headers = {};
iapSetupStub = sinon.stub(iapModule, 'setup')
.returnsPromise().resolves();
.resolves();
iapValidateStub = sinon.stub(iapModule, 'validate')
.returnsPromise().resolves({});
.resolves({});
iapIsValidatedStub = sinon.stub(iapModule, 'isValidated')
.returns(true);
iapGetPurchaseDataStub = sinon.stub(iapModule, 'getPurchaseData')
.returns([{productId: 'com.habitrpg.ios.Habitica.21gems',
transactionId: token,
}]);
paymentBuyGemsStub = sinon.stub(payments, 'buyGems').returnsPromise().resolves({});
paymentBuyGemsStub = sinon.stub(payments, 'buyGems').resolves({});
});
afterEach(() => {
@@ -70,7 +70,7 @@ describe('Apple Payments', () => {
});
it('errors if the user cannot purchase gems', async () => {
sinon.stub(user, 'canGetGems').returnsPromise().resolves(false);
sinon.stub(user, 'canGetGems').resolves(false);
await expect(applePayments.verifyGemPurchase(user, receipt, headers))
.to.eventually.be.rejected.and.to.eql({
httpCode: 401,
@@ -82,7 +82,7 @@ describe('Apple Payments', () => {
});
it('errors if amount does not exist', async () => {
sinon.stub(user, 'canGetGems').returnsPromise().resolves(true);
sinon.stub(user, 'canGetGems').resolves(true);
iapGetPurchaseDataStub.restore();
iapGetPurchaseDataStub = sinon.stub(iapModule, 'getPurchaseData')
.returns([{productId: 'badProduct',
@@ -130,7 +130,7 @@ describe('Apple Payments', () => {
transactionId: token,
}]);
sinon.stub(user, 'canGetGems').returnsPromise().resolves(true);
sinon.stub(user, 'canGetGems').resolves(true);
await applePayments.verifyGemPurchase(user, receipt, headers);
expect(iapSetupStub).to.be.calledOnce;
@@ -167,9 +167,9 @@ describe('Apple Payments', () => {
nextPaymentProcessing = moment.utc().add({days: 2});
iapSetupStub = sinon.stub(iapModule, 'setup')
.returnsPromise().resolves();
.resolves();
iapValidateStub = sinon.stub(iapModule, 'validate')
.returnsPromise().resolves({});
.resolves({});
iapIsValidatedStub = sinon.stub(iapModule, 'isValidated')
.returns(true);
iapGetPurchaseDataStub = sinon.stub(iapModule, 'getPurchaseData')
@@ -186,7 +186,7 @@ describe('Apple Payments', () => {
productId: sku,
transactionId: token,
}]);
paymentsCreateSubscritionStub = sinon.stub(payments, 'createSubscription').returnsPromise().resolves({});
paymentsCreateSubscritionStub = sinon.stub(payments, 'createSubscription').resolves({});
});
afterEach(() => {
@@ -297,9 +297,9 @@ describe('Apple Payments', () => {
expirationDate = moment.utc();
iapSetupStub = sinon.stub(iapModule, 'setup')
.returnsPromise().resolves();
.resolves();
iapValidateStub = sinon.stub(iapModule, 'validate')
.returnsPromise().resolves({
.resolves({
expirationDate,
});
iapGetPurchaseDataStub = sinon.stub(iapModule, 'getPurchaseData')
@@ -314,7 +314,7 @@ describe('Apple Payments', () => {
user.purchased.plan.planId = subKey;
user.purchased.plan.additionalData = receipt;
paymentCancelSubscriptionSpy = sinon.stub(payments, 'cancelSubscription').returnsPromise().resolves({});
paymentCancelSubscriptionSpy = sinon.stub(payments, 'cancelSubscription').resolves({});
});
afterEach(function () {

View File

@@ -24,12 +24,12 @@ describe('Google Payments', () => {
headers = {};
iapSetupStub = sinon.stub(iapModule, 'setup')
.returnsPromise().resolves();
.resolves();
iapValidateStub = sinon.stub(iapModule, 'validate')
.returnsPromise().resolves({});
.resolves({});
iapIsValidatedStub = sinon.stub(iapModule, 'isValidated')
.returns(true);
paymentBuyGemsStub = sinon.stub(payments, 'buyGems').returnsPromise().resolves({});
paymentBuyGemsStub = sinon.stub(payments, 'buyGems').resolves({});
});
afterEach(() => {
@@ -64,7 +64,7 @@ describe('Google Payments', () => {
});
it('should throw an error if user cannot purchase gems', async () => {
sinon.stub(user, 'canGetGems').returnsPromise().resolves(false);
sinon.stub(user, 'canGetGems').resolves(false);
await expect(googlePayments.verifyGemPurchase(user, receipt, signature, headers))
.to.eventually.be.rejected.and.to.eql({
@@ -77,7 +77,7 @@ describe('Google Payments', () => {
});
it('purchases gems', async () => {
sinon.stub(user, 'canGetGems').returnsPromise().resolves(true);
sinon.stub(user, 'canGetGems').resolves(true);
await googlePayments.verifyGemPurchase(user, receipt, signature, headers);
expect(iapSetupStub).to.be.calledOnce;
@@ -116,12 +116,12 @@ describe('Google Payments', () => {
nextPaymentProcessing = moment.utc().add({days: 2});
iapSetupStub = sinon.stub(iapModule, 'setup')
.returnsPromise().resolves();
.resolves();
iapValidateStub = sinon.stub(iapModule, 'validate')
.returnsPromise().resolves({});
.resolves({});
iapIsValidatedStub = sinon.stub(iapModule, 'isValidated')
.returns(true);
paymentsCreateSubscritionStub = sinon.stub(payments, 'createSubscription').returnsPromise().resolves({});
paymentsCreateSubscritionStub = sinon.stub(payments, 'createSubscription').resolves({});
});
afterEach(() => {
@@ -193,9 +193,9 @@ describe('Google Payments', () => {
expirationDate = moment.utc();
iapSetupStub = sinon.stub(iapModule, 'setup')
.returnsPromise().resolves();
.resolves();
iapValidateStub = sinon.stub(iapModule, 'validate')
.returnsPromise().resolves({
.resolves({
expirationDate,
});
iapGetPurchaseDataStub = sinon.stub(iapModule, 'getPurchaseData')
@@ -210,7 +210,7 @@ describe('Google Payments', () => {
user.purchased.plan.planId = subKey;
user.purchased.plan.additionalData = {data: receipt, signature};
paymentCancelSubscriptionSpy = sinon.stub(payments, 'cancelSubscription').returnsPromise().resolves({});
paymentCancelSubscriptionSpy = sinon.stub(payments, 'cancelSubscription').resolves({});
});
afterEach(function () {

View File

@@ -69,11 +69,11 @@ describe('Purchasing a group plan for group', () => {
};
let subscriptionId = 'subId';
sinon.stub(stripe.customers, 'del').returnsPromise().resolves({});
sinon.stub(stripe.customers, 'del').resolves({});
let currentPeriodEndTimeStamp = moment().add(3, 'months').unix();
sinon.stub(stripe.customers, 'retrieve')
.returnsPromise().resolves({
.resolves({
subscriptions: {
data: [{id: subscriptionId, current_period_end: currentPeriodEndTimeStamp}], // eslint-disable-line camelcase
},
@@ -216,7 +216,6 @@ describe('Purchasing a group plan for group', () => {
it('sends one email to subscribed member of group, stating subscription is cancelled (Amazon)', async () => {
sinon.stub(amzLib, 'getBillingAgreementDetails')
.returnsPromise()
.resolves({
BillingAgreementDetails: {
BillingAgreementStatus: {State: 'Closed'},
@@ -251,9 +250,9 @@ describe('Purchasing a group plan for group', () => {
});
it('sends one email to subscribed member of group, stating subscription is cancelled (PayPal)', async () => {
sinon.stub(paypalPayments, 'paypalBillingAgreementCancel').returnsPromise().resolves({});
sinon.stub(paypalPayments, 'paypalBillingAgreementCancel').resolves({});
sinon.stub(paypalPayments, 'paypalBillingAgreementGet')
.returnsPromise().resolves({
.resolves({
agreement_details: { // eslint-disable-line camelcase
next_billing_date: moment().add(3, 'months').toDate(), // eslint-disable-line camelcase
cycles_completed: 1, // eslint-disable-line camelcase
@@ -449,7 +448,6 @@ describe('Purchasing a group plan for group', () => {
it('adds months to members with existing recurring subscription (Amazon)', async () => {
sinon.stub(amzLib, 'getBillingAgreementDetails')
.returnsPromise()
.resolves({
BillingAgreementDetails: {
BillingAgreementStatus: {State: 'Closed'},
@@ -478,9 +476,9 @@ describe('Purchasing a group plan for group', () => {
});
it('adds months to members with existing recurring subscription (Paypal)', async () => {
sinon.stub(paypalPayments, 'paypalBillingAgreementCancel').returnsPromise().resolves({});
sinon.stub(paypalPayments, 'paypalBillingAgreementCancel').resolves({});
sinon.stub(paypalPayments, 'paypalBillingAgreementGet')
.returnsPromise().resolves({
.resolves({
agreement_details: { // eslint-disable-line camelcase
next_billing_date: moment().add(3, 'months').toDate(), // eslint-disable-line camelcase
cycles_completed: 1, // eslint-disable-line camelcase

View File

@@ -209,7 +209,7 @@ describe('payments/index', () => {
await api.createSubscription(data);
let msg = '\`Hello recipient, sender has sent you 3 months of subscription!\`';
expect(user.sendMessage).to.be.calledOnce;
expect(user.sendMessage).to.be.calledTwice;
expect(user.sendMessage).to.be.calledWith(recipient, { receiverMsg: msg, senderMsg: msg, save: false });
});
@@ -247,6 +247,77 @@ describe('payments/index', () => {
},
});
});
context('Winter 2018-19 Gift-1-Get-1 Promotion', async () => {
it('creates a gift subscription for purchaser and recipient if none exist', async () => {
await api.createSubscription(data);
expect(user.items.pets['Jackalope-RoyalPurple']).to.eql(5);
expect(user.purchased.plan.customerId).to.eql('Gift');
expect(user.purchased.plan.dateTerminated).to.exist;
expect(user.purchased.plan.dateUpdated).to.exist;
expect(user.purchased.plan.dateCreated).to.exist;
expect(recipient.items.pets['Jackalope-RoyalPurple']).to.eql(5);
expect(recipient.purchased.plan.customerId).to.eql('Gift');
expect(recipient.purchased.plan.dateTerminated).to.exist;
expect(recipient.purchased.plan.dateUpdated).to.exist;
expect(recipient.purchased.plan.dateCreated).to.exist;
});
it('adds extraMonths to existing subscription for purchaser and creates a gift subscription for recipient without sub', async () => {
user.purchased.plan = plan;
expect(user.purchased.plan.extraMonths).to.eql(0);
await api.createSubscription(data);
expect(user.purchased.plan.extraMonths).to.eql(3);
expect(recipient.items.pets['Jackalope-RoyalPurple']).to.eql(5);
expect(recipient.purchased.plan.customerId).to.eql('Gift');
expect(recipient.purchased.plan.dateTerminated).to.exist;
expect(recipient.purchased.plan.dateUpdated).to.exist;
expect(recipient.purchased.plan.dateCreated).to.exist;
});
it('adds extraMonths to existing subscription for recipient and creates a gift subscription for purchaser without sub', async () => {
recipient.purchased.plan = plan;
expect(recipient.purchased.plan.extraMonths).to.eql(0);
await api.createSubscription(data);
expect(recipient.purchased.plan.extraMonths).to.eql(3);
expect(user.items.pets['Jackalope-RoyalPurple']).to.eql(5);
expect(user.purchased.plan.customerId).to.eql('Gift');
expect(user.purchased.plan.dateTerminated).to.exist;
expect(user.purchased.plan.dateUpdated).to.exist;
expect(user.purchased.plan.dateCreated).to.exist;
});
it('adds extraMonths to existing subscriptions for purchaser and recipient', async () => {
user.purchased.plan = plan;
recipient.purchased.plan = plan;
expect(user.purchased.plan.extraMonths).to.eql(0);
expect(recipient.purchased.plan.extraMonths).to.eql(0);
await api.createSubscription(data);
expect(user.purchased.plan.extraMonths).to.eql(3);
expect(recipient.purchased.plan.extraMonths).to.eql(3);
});
it('sends a private message about the promotion', async () => {
await api.createSubscription(data);
let msg = '\`Hello sender, you received 3 months of subscription as part of our holiday gift-giving promotion!\`';
expect(user.sendMessage).to.be.calledTwice;
expect(user.sendMessage).to.be.calledWith(user, { senderMsg: msg });
});
});
});
context('Purchasing a subscription for self', () => {
@@ -446,6 +517,19 @@ describe('payments/index', () => {
fakeClock.restore();
});
it('does not add a notification for mystery items if none was awarded', async () => {
const noMysteryItemTimeframe = 1462183920000; // May 2nd 2016
let fakeClock = sinon.useFakeTimers(noMysteryItemTimeframe);
data = { paymentMethod: 'PaymentMethod', user, sub: { key: 'basic_3mo' } };
await api.createSubscription(data);
expect(user.purchased.plan.mysteryItems).to.have.a.lengthOf(0);
expect(user.notifications.find(n => n.type === 'NEW_MYSTERY_ITEMS')).to.be.undefined;
fakeClock.restore();
});
it('does not award mystery item when user already owns the item', async () => {
let mayMysteryItemTimeframe = 1464725113000; // May 31st 2016
let fakeClock = sinon.useFakeTimers(mayMysteryItemTimeframe);

View File

@@ -13,9 +13,9 @@ describe('checkout success', () => {
customerId = 'customerId-test';
paymentId = 'paymentId-test';
paypalPaymentExecuteStub = sinon.stub(paypalPayments, 'paypalPaymentExecute').returnsPromise().resolves({});
paymentBuyGemsStub = sinon.stub(payments, 'buyGems').returnsPromise().resolves({});
paymentsCreateSubscritionStub = sinon.stub(payments, 'createSubscription').returnsPromise().resolves({});
paypalPaymentExecuteStub = sinon.stub(paypalPayments, 'paypalPaymentExecute').resolves({});
paymentBuyGemsStub = sinon.stub(payments, 'buyGems').resolves({});
paymentsCreateSubscritionStub = sinon.stub(payments, 'createSubscription').resolves({});
});
afterEach(() => {

View File

@@ -15,6 +15,7 @@ describe('checkout', () => {
function getPaypalCreateOptions (description, amount) {
return {
experience_profile_id: 'xp_profile_id',
intent: 'sale',
payer: { payment_method: 'Paypal' },
redirect_urls: {
@@ -42,7 +43,7 @@ describe('checkout', () => {
beforeEach(() => {
approvalHerf = 'approval_href';
paypalPaymentCreateStub = sinon.stub(paypalPayments, 'paypalPaymentCreate')
.returnsPromise().resolves({
.resolves({
links: [{ rel: 'approval_url', href: approvalHerf }],
});
});
@@ -80,7 +81,7 @@ describe('checkout', () => {
it('should error if the user cannot get gems', async () => {
let user = new User();
sinon.stub(user, 'canGetGems').returnsPromise().resolves(false);
sinon.stub(user, 'canGetGems').resolves(false);
await expect(paypalPayments.checkout({user})).to.eventually.be.rejected.and.to.eql({
httpCode: 401,

View File

@@ -34,8 +34,8 @@ describe('ipn', () => {
group.purchased.plan.lastBillingDate = new Date();
await group.save();
ipnVerifyAsyncStub = sinon.stub(paypalPayments, 'ipnVerifyAsync').returnsPromise().resolves({});
paymentCancelSubscriptionSpy = sinon.stub(payments, 'cancelSubscription').returnsPromise().resolves({});
ipnVerifyAsyncStub = sinon.stub(paypalPayments, 'ipnVerifyAsync').resolves({});
paymentCancelSubscriptionSpy = sinon.stub(payments, 'cancelSubscription').resolves({});
});
afterEach(function () {

View File

@@ -38,15 +38,15 @@ describe('subscribeCancel', () => {
nextBillingDate = new Date();
paypalBillingAgreementCancelStub = sinon.stub(paypalPayments, 'paypalBillingAgreementCancel').returnsPromise().resolves({});
paypalBillingAgreementCancelStub = sinon.stub(paypalPayments, 'paypalBillingAgreementCancel').resolves({});
paypalBillingAgreementGetStub = sinon.stub(paypalPayments, 'paypalBillingAgreementGet')
.returnsPromise().resolves({
.resolves({
agreement_details: {
next_billing_date: nextBillingDate,
cycles_completed: 1,
},
});
paymentCancelSubscriptionSpy = sinon.stub(payments, 'cancelSubscription').returnsPromise().resolves({});
paymentCancelSubscriptionSpy = sinon.stub(payments, 'cancelSubscription').resolves({});
});
afterEach(function () {

View File

@@ -28,10 +28,10 @@ describe('subscribeSuccess', () => {
customerId = 'test-customerId';
paypalBillingAgreementExecuteStub = sinon.stub(paypalPayments, 'paypalBillingAgreementExecute')
.returnsPromise({}).resolves({
.resolves({
id: customerId,
});
paymentsCreateSubscritionStub = sinon.stub(payments, 'createSubscription').returnsPromise().resolves({});
paymentsCreateSubscritionStub = sinon.stub(payments, 'createSubscription').resolves({});
});
afterEach(() => {

View File

@@ -18,7 +18,7 @@ describe('subscribe', () => {
sub = Object.assign({}, common.content.subscriptionBlocks[subKey]);
paypalBillingAgreementCreateStub = sinon.stub(paypalPayments, 'paypalBillingAgreementCreate')
.returnsPromise().resolves({
.resolves({
links: [{ rel: 'approval_url', href: approvalHerf }],
});
});

View File

@@ -82,12 +82,12 @@ describe('cancel subscription', () => {
beforeEach(() => {
subscriptionId = 'subId';
stripeDeleteCustomerStub = sinon.stub(stripe.customers, 'del').returnsPromise().resolves({});
paymentsCancelSubStub = sinon.stub(payments, 'cancelSubscription').returnsPromise().resolves({});
stripeDeleteCustomerStub = sinon.stub(stripe.customers, 'del').resolves({});
paymentsCancelSubStub = sinon.stub(payments, 'cancelSubscription').resolves({});
currentPeriodEndTimeStamp = (new Date()).getTime();
stripeRetrieveStub = sinon.stub(stripe.customers, 'retrieve')
.returnsPromise().resolves({
.resolves({
subscriptions: {
data: [{id: subscriptionId, current_period_end: currentPeriodEndTimeStamp}], // eslint-disable-line camelcase
},

View File

@@ -54,7 +54,7 @@ describe('checkout with subscription', () => {
token = 'test-token';
spy = sinon.stub(stripe.subscriptions, 'update');
spy.returnsPromise().resolves;
spy.resolves;
stripeCreateCustomerSpy = sinon.stub(stripe.customers, 'create');
let stripCustomerResponse = {
@@ -63,10 +63,10 @@ describe('checkout with subscription', () => {
data: [{id: subscriptionId}],
},
};
stripeCreateCustomerSpy.returnsPromise().resolves(stripCustomerResponse);
stripeCreateCustomerSpy.resolves(stripCustomerResponse);
stripePaymentsCreateSubSpy = sinon.stub(payments, 'createSubscription');
stripePaymentsCreateSubSpy.returnsPromise().resolves({});
stripePaymentsCreateSubSpy.resolves({});
data.groupId = group._id;
data.sub.quantity = 3;

View File

@@ -26,9 +26,9 @@ describe('checkout', () => {
let stripCustomerResponse = {
id: customerIdResponse,
};
stripeChargeStub = sinon.stub(stripe.charges, 'create').returnsPromise().resolves(stripCustomerResponse);
paymentBuyGemsStub = sinon.stub(payments, 'buyGems').returnsPromise().resolves({});
paymentCreateSubscritionStub = sinon.stub(payments, 'createSubscription').returnsPromise().resolves({});
stripeChargeStub = sinon.stub(stripe.charges, 'create').resolves(stripCustomerResponse);
paymentBuyGemsStub = sinon.stub(payments, 'buyGems').resolves({});
paymentCreateSubscritionStub = sinon.stub(payments, 'createSubscription').resolves({});
});
afterEach(() => {
@@ -82,7 +82,7 @@ describe('checkout', () => {
it('should error if user cannot get gems', async () => {
gift = undefined;
sinon.stub(user, 'canGetGems').returnsPromise().resolves(false);
sinon.stub(user, 'canGetGems').resolves(false);
await expect(stripePayments.checkout({
token,
@@ -101,7 +101,7 @@ describe('checkout', () => {
it('should purchase gems', async () => {
gift = undefined;
sinon.stub(user, 'canGetGems').returnsPromise().resolves(true);
sinon.stub(user, 'canGetGems').resolves(true);
await stripePayments.checkout({
token,

View File

@@ -98,11 +98,11 @@ describe('edit subscription', () => {
beforeEach(() => {
subscriptionId = 'subId';
stripeListSubscriptionStub = sinon.stub(stripe.customers, 'listSubscriptions')
.returnsPromise().resolves({
.resolves({
data: [{id: subscriptionId}],
});
stripeUpdateSubscriptionStub = sinon.stub(stripe.customers, 'updateSubscription').returnsPromise().resolves({});
stripeUpdateSubscriptionStub = sinon.stub(stripe.customers, 'updateSubscription').resolves({});
});
afterEach(() => {

View File

@@ -22,7 +22,7 @@ describe('Stripe - Webhooks', () => {
const eventRetrieved = {type: eventType};
beforeEach(() => {
sinon.stub(stripe.events, 'retrieve').returnsPromise().resolves(eventRetrieved);
sinon.stub(stripe.events, 'retrieve').resolves(eventRetrieved);
sinon.stub(logger, 'error');
});
@@ -52,8 +52,8 @@ describe('Stripe - Webhooks', () => {
const eventType = 'customer.subscription.deleted';
beforeEach(() => {
sinon.stub(stripe.customers, 'del').returnsPromise().resolves({});
sinon.stub(payments, 'cancelSubscription').returnsPromise().resolves({});
sinon.stub(stripe.customers, 'del').resolves({});
sinon.stub(payments, 'cancelSubscription').resolves({});
});
afterEach(() => {
@@ -62,7 +62,7 @@ describe('Stripe - Webhooks', () => {
});
it('does not do anything if event.request is null (subscription cancelled manually)', async () => {
sinon.stub(stripe.events, 'retrieve').returnsPromise().resolves({
sinon.stub(stripe.events, 'retrieve').resolves({
id: 123,
type: eventType,
request: 123,
@@ -79,7 +79,7 @@ describe('Stripe - Webhooks', () => {
describe('user subscription', () => {
it('throws an error if the user is not found', async () => {
const customerId = 456;
sinon.stub(stripe.events, 'retrieve').returnsPromise().resolves({
sinon.stub(stripe.events, 'retrieve').resolves({
id: 123,
type: eventType,
data: {
@@ -113,7 +113,7 @@ describe('Stripe - Webhooks', () => {
subscriber.purchased.plan.paymentMethod = 'Stripe';
await subscriber.save();
sinon.stub(stripe.events, 'retrieve').returnsPromise().resolves({
sinon.stub(stripe.events, 'retrieve').resolves({
id: 123,
type: eventType,
data: {
@@ -146,7 +146,7 @@ describe('Stripe - Webhooks', () => {
describe('group plan subscription', () => {
it('throws an error if the group is not found', async () => {
const customerId = 456;
sinon.stub(stripe.events, 'retrieve').returnsPromise().resolves({
sinon.stub(stripe.events, 'retrieve').resolves({
id: 123,
type: eventType,
data: {
@@ -185,7 +185,7 @@ describe('Stripe - Webhooks', () => {
subscriber.purchased.plan.paymentMethod = 'Stripe';
await subscriber.save();
sinon.stub(stripe.events, 'retrieve').returnsPromise().resolves({
sinon.stub(stripe.events, 'retrieve').resolves({
id: 123,
type: eventType,
data: {
@@ -227,7 +227,7 @@ describe('Stripe - Webhooks', () => {
subscriber.purchased.plan.paymentMethod = 'Stripe';
await subscriber.save();
sinon.stub(stripe.events, 'retrieve').returnsPromise().resolves({
sinon.stub(stripe.events, 'retrieve').resolves({
id: 123,
type: eventType,
data: {

View File

@@ -38,7 +38,7 @@ describe('Stripe - Upgrade Group Plan', () => {
await group.save();
spy = sinon.stub(stripe.subscriptions, 'update');
spy.returnsPromise().resolves([]);
spy.resolves([]);
data.groupId = group._id;
data.sub.quantity = 3;
stripePayments.setStripeApi(stripe);

View File

@@ -110,7 +110,7 @@ describe('slack', () => {
});
it('noops if no flagging url is provided', () => {
sandbox.stub(nconf, 'get').withArgs('SLACK:FLAGGING_URL').returns('');
sandbox.stub(nconf, 'get').withArgs('SLACK_FLAGGING_URL').returns('');
sandbox.stub(logger, 'error');
let reRequiredSlack = requireAgain('../../../../website/server/libs/slack');

View File

@@ -477,7 +477,7 @@ describe('Group Model', () => {
party.quest.active = false;
await party.startQuest(questLeader);
Group.prototype.sendChat.reset();
Group.prototype.sendChat.resetHistory();
await party.save();
await Group.processQuestProgress(participatingMember, progress);
@@ -496,7 +496,7 @@ describe('Group Model', () => {
party.quest.active = false;
await party.startQuest(questLeader);
Group.prototype.sendChat.reset();
Group.prototype.sendChat.resetHistory();
await party.save();
await Group.processQuestProgress(participatingMember, progress);
@@ -569,7 +569,7 @@ describe('Group Model', () => {
});
it('throws an error if no uuids or emails are passed in', async () => {
await expect(Group.validateInvitations(null, null, res)).to.eventually.be.rejected.and.eql({
await expect(Group.validateInvitations({}, res)).to.eventually.be.rejected.and.eql({
httpCode: 400,
message: 'Bad request.',
name: 'BadRequest',
@@ -579,7 +579,7 @@ describe('Group Model', () => {
});
it('throws an error if only uuids are passed in, but they are not an array', async () => {
await expect(Group.validateInvitations({ uuid: 'user-id'}, null, res)).to.eventually.be.rejected.and.eql({
await expect(Group.validateInvitations({ uuids: 'user-id'}, res)).to.eventually.be.rejected.and.eql({
httpCode: 400,
message: 'Bad request.',
name: 'BadRequest',
@@ -589,7 +589,7 @@ describe('Group Model', () => {
});
it('throws an error if only emails are passed in, but they are not an array', async () => {
await expect(Group.validateInvitations(null, { emails: 'user@example.com'}, res)).to.eventually.be.rejected.and.eql({
await expect(Group.validateInvitations({emails: 'user@example.com'}, res)).to.eventually.be.rejected.and.eql({
httpCode: 400,
message: 'Bad request.',
name: 'BadRequest',
@@ -599,27 +599,27 @@ describe('Group Model', () => {
});
it('throws an error if emails are not passed in, and uuid array is empty', async () => {
await expect(Group.validateInvitations([], null, res)).to.eventually.be.rejected.and.eql({
await expect(Group.validateInvitations({uuids: []}, res)).to.eventually.be.rejected.and.eql({
httpCode: 400,
message: 'Bad request.',
name: 'BadRequest',
});
expect(res.t).to.be.calledOnce;
expect(res.t).to.be.calledWith('inviteMissingUuid');
expect(res.t).to.be.calledWith('inviteMustNotBeEmpty');
});
it('throws an error if uuids are not passed in, and email array is empty', async () => {
await expect(Group.validateInvitations(null, [], res)).to.eventually.be.rejected.and.eql({
await expect(Group.validateInvitations({emails: []}, res)).to.eventually.be.rejected.and.eql({
httpCode: 400,
message: 'Bad request.',
name: 'BadRequest',
});
expect(res.t).to.be.calledOnce;
expect(res.t).to.be.calledWith('inviteMissingEmail');
expect(res.t).to.be.calledWith('inviteMustNotBeEmpty');
});
it('throws an error if uuids and emails are passed in as empty arrays', async () => {
await expect(Group.validateInvitations([], [], res)).to.eventually.be.rejected.and.eql({
await expect(Group.validateInvitations({emails: [], uuids: []}, res)).to.eventually.be.rejected.and.eql({
httpCode: 400,
message: 'Bad request.',
name: 'BadRequest',
@@ -639,7 +639,7 @@ describe('Group Model', () => {
uuids.push('one-more-uuid'); // to put it over the limit
await expect(Group.validateInvitations(uuids, emails, res)).to.eventually.be.rejected.and.eql({
await expect(Group.validateInvitations({uuids, emails}, res)).to.eventually.be.rejected.and.eql({
httpCode: 400,
message: 'Bad request.',
name: 'BadRequest',
@@ -657,33 +657,33 @@ describe('Group Model', () => {
emails.push(`user-${i}@example.com`);
}
await Group.validateInvitations(uuids, emails, res);
await Group.validateInvitations({uuids, emails}, res);
expect(res.t).to.not.be.called;
});
it('does not throw an error if only user ids are passed in', async () => {
await Group.validateInvitations(['user-id', 'user-id2'], null, res);
await Group.validateInvitations({uuids: ['user-id', 'user-id2']}, res);
expect(res.t).to.not.be.called;
});
it('does not throw an error if only emails are passed in', async () => {
await Group.validateInvitations(null, ['user1@example.com', 'user2@example.com'], res);
await Group.validateInvitations({emails: ['user1@example.com', 'user2@example.com']}, res);
expect(res.t).to.not.be.called;
});
it('does not throw an error if both uuids and emails are passed in', async () => {
await Group.validateInvitations(['user-id', 'user-id2'], ['user1@example.com', 'user2@example.com'], res);
await Group.validateInvitations({uuids: ['user-id', 'user-id2'], emails: ['user1@example.com', 'user2@example.com']}, res);
expect(res.t).to.not.be.called;
});
it('does not throw an error if uuids are passed in and emails are an empty array', async () => {
await Group.validateInvitations(['user-id', 'user-id2'], [], res);
await Group.validateInvitations({uuids: ['user-id', 'user-id2'], emails: []}, res);
expect(res.t).to.not.be.called;
});
it('does not throw an error if emails are passed in and uuids are an empty array', async () => {
await Group.validateInvitations([], ['user1@example.com', 'user2@example.com'], res);
await Group.validateInvitations({uuids: [], emails: ['user1@example.com', 'user2@example.com']}, res);
expect(res.t).to.not.be.called;
});
});
@@ -1843,6 +1843,62 @@ describe('Group Model', () => {
expect(options.chat).to.eql(chat);
});
it('sends webhooks for users with webhooks triggered by system messages', async () => {
let guild = new Group({
name: 'some guild',
type: 'guild',
});
let memberWithWebhook = new User({
guilds: [guild._id],
webhooks: [{
type: 'groupChatReceived',
url: 'http://someurl.com',
options: {
groupId: guild._id,
},
}],
});
let memberWithoutWebhook = new User({
guilds: [guild._id],
});
let nonMemberWithWebhooks = new User({
webhooks: [{
type: 'groupChatReceived',
url: 'http://a-different-url.com',
options: {
groupId: generateUUID(),
},
}],
});
await Promise.all([
memberWithWebhook.save(),
memberWithoutWebhook.save(),
nonMemberWithWebhooks.save(),
]);
guild.leader = memberWithWebhook._id;
await guild.save();
const groupMessage = guild.sendChat('Test message.');
await groupMessage.save();
await sleep();
expect(groupChatReceivedWebhook.send).to.be.calledOnce;
let args = groupChatReceivedWebhook.send.args[0];
let webhooks = args[0].webhooks;
let options = args[1];
expect(webhooks).to.have.a.lengthOf(1);
expect(webhooks[0].id).to.eql(memberWithWebhook.webhooks[0].id);
expect(options.group).to.eql(guild);
expect(options.chat).to.eql(groupMessage);
});
it('sends webhooks for each user with webhooks in group', async () => {
let guild = new Group({
name: 'some guild',
@@ -1932,28 +1988,54 @@ describe('Group Model', () => {
context('hasNotCancelled', () => {
it('returns false if group does not have customer id', () => {
expect(party.hasNotCancelled()).to.be.undefined;
expect(party.hasNotCancelled()).to.be.false;
});
it('returns true if party does not have plan.dateTerminated', () => {
it('returns true if group does not have plan.dateTerminated', () => {
party.purchased.plan.customerId = 'test-id';
expect(party.hasNotCancelled()).to.be.true;
});
it('returns false if party if plan.dateTerminated is after today', () => {
it('returns false if group if plan.dateTerminated is after today', () => {
party.purchased.plan.customerId = 'test-id';
party.purchased.plan.dateTerminated = moment().add(1, 'days').toDate();
expect(party.hasNotCancelled()).to.be.false;
});
it('returns false if party if plan.dateTerminated is before today', () => {
it('returns false if group if plan.dateTerminated is before today', () => {
party.purchased.plan.customerId = 'test-id';
party.purchased.plan.dateTerminated = moment().subtract(1, 'days').toDate();
expect(party.hasNotCancelled()).to.be.false;
});
});
context('hasCancelled', () => {
it('returns false if group does not have customer id', () => {
expect(party.hasCancelled()).to.be.false;
});
it('returns false if group does not have plan.dateTerminated', () => {
party.purchased.plan.customerId = 'test-id';
expect(party.hasCancelled()).to.be.false;
});
it('returns true if group if plan.dateTerminated is after today', () => {
party.purchased.plan.customerId = 'test-id';
party.purchased.plan.dateTerminated = moment().add(1, 'days').toDate();
expect(party.hasCancelled()).to.be.true;
});
it('returns false if group if plan.dateTerminated is before today', () => {
party.purchased.plan.customerId = 'test-id';
party.purchased.plan.dateTerminated = moment().subtract(1, 'days').toDate();
expect(party.hasCancelled()).to.be.false;
});
});
});
});

View File

@@ -315,9 +315,8 @@ describe('User Model', () => {
user = new User();
});
it('returns false if user does not have customer id', () => {
expect(user.hasNotCancelled()).to.be.undefined;
expect(user.hasNotCancelled()).to.be.false;
});
it('returns true if user does not have plan.dateTerminated', () => {
@@ -341,6 +340,38 @@ describe('User Model', () => {
});
});
context('hasCancelled', () => {
let user;
beforeEach(() => {
user = new User();
});
it('returns false if user does not have customer id', () => {
expect(user.hasCancelled()).to.be.false;
});
it('returns false if user does not have plan.dateTerminated', () => {
user.purchased.plan.customerId = 'test-id';
expect(user.hasCancelled()).to.be.false;
});
it('returns true if user if plan.dateTerminated is after today', () => {
user.purchased.plan.customerId = 'test-id';
user.purchased.plan.dateTerminated = moment().add(1, 'days').toDate();
expect(user.hasCancelled()).to.be.true;
});
it('returns false if user if plan.dateTerminated is before today', () => {
user.purchased.plan.customerId = 'test-id';
user.purchased.plan.dateTerminated = moment().subtract(1, 'days').toDate();
expect(user.hasCancelled()).to.be.false;
});
});
context('pre-save hook', () => {
it('does not try to award achievements when achievements or items not selected in query', async () => {
let user = new User();

View File

@@ -47,6 +47,14 @@ describe('GET /challenges/:challengeId', () => {
_id: groupLeader._id,
id: groupLeader._id,
profile: {name: groupLeader.profile.name},
auth: {
local: {
username: groupLeader.auth.local.username,
},
},
flags: {
verifiedUsername: true,
},
});
expect(chal.group).to.eql({
_id: group._id,
@@ -105,6 +113,14 @@ describe('GET /challenges/:challengeId', () => {
_id: challengeLeader._id,
id: challengeLeader._id,
profile: {name: challengeLeader.profile.name},
auth: {
local: {
username: challengeLeader.auth.local.username,
},
},
flags: {
verifiedUsername: true,
},
});
expect(chal.group).to.eql({
_id: group._id,
@@ -131,6 +147,14 @@ describe('GET /challenges/:challengeId', () => {
_id: challengeLeader._id,
id: challengeLeader._id,
profile: {name: challengeLeader.profile.name},
auth: {
local: {
username: challengeLeader.auth.local.username,
},
},
flags: {
verifiedUsername: true,
},
});
});
});
@@ -179,6 +203,14 @@ describe('GET /challenges/:challengeId', () => {
_id: challengeLeader._id,
id: challengeLeader._id,
profile: {name: challengeLeader.profile.name},
auth: {
local: {
username: challengeLeader.auth.local.username,
},
},
flags: {
verifiedUsername: true,
},
});
expect(chal.group).to.eql({
_id: group._id,
@@ -205,6 +237,14 @@ describe('GET /challenges/:challengeId', () => {
_id: challengeLeader._id,
id: challengeLeader._id,
profile: {name: challengeLeader.profile.name},
auth: {
local: {
username: challengeLeader.auth.local.username,
},
},
flags: {
verifiedUsername: true,
},
});
});
});

View File

@@ -60,6 +60,14 @@ describe('GET /challenges/:challengeId/members', () => {
_id: groupLeader._id,
id: groupLeader._id,
profile: {name: groupLeader.profile.name},
auth: {
local: {
username: groupLeader.auth.local.username,
},
},
flags: {
verifiedUsername: true,
},
});
});
@@ -73,8 +81,16 @@ describe('GET /challenges/:challengeId/members', () => {
_id: leader._id,
id: leader._id,
profile: {name: leader.profile.name},
auth: {
local: {
username: leader.auth.local.username,
},
},
flags: {
verifiedUsername: true,
},
});
expect(res[0]).to.have.all.keys(['_id', 'id', 'profile']);
expect(res[0]).to.have.all.keys(['_id', 'auth', 'flags', 'id', 'profile']);
expect(res[0].profile).to.have.all.keys(['name']);
});
@@ -88,8 +104,16 @@ describe('GET /challenges/:challengeId/members', () => {
_id: anotherUser._id,
id: anotherUser._id,
profile: {name: anotherUser.profile.name},
auth: {
local: {
username: anotherUser.auth.local.username,
},
},
flags: {
verifiedUsername: true,
},
});
expect(res[0]).to.have.all.keys(['_id', 'id', 'profile']);
expect(res[0]).to.have.all.keys(['_id', 'auth', 'flags', 'id', 'profile']);
expect(res[0].profile).to.have.all.keys(['name']);
});
@@ -107,7 +131,7 @@ describe('GET /challenges/:challengeId/members', () => {
let res = await user.get(`/challenges/${challenge._id}/members?includeAllMembers=not-true`);
expect(res.length).to.equal(30);
res.forEach(member => {
expect(member).to.have.all.keys(['_id', 'id', 'profile']);
expect(member).to.have.all.keys(['_id', 'auth', 'flags', 'id', 'profile']);
expect(member.profile).to.have.all.keys(['name']);
});
});
@@ -126,7 +150,7 @@ describe('GET /challenges/:challengeId/members', () => {
let res = await user.get(`/challenges/${challenge._id}/members`);
expect(res.length).to.equal(30);
res.forEach(member => {
expect(member).to.have.all.keys(['_id', 'id', 'profile']);
expect(member).to.have.all.keys(['_id', 'auth', 'flags', 'id', 'profile']);
expect(member.profile).to.have.all.keys(['name']);
});
});
@@ -145,7 +169,7 @@ describe('GET /challenges/:challengeId/members', () => {
let res = await user.get(`/challenges/${challenge._id}/members?includeAllMembers=true`);
expect(res.length).to.equal(32);
res.forEach(member => {
expect(member).to.have.all.keys(['_id', 'id', 'profile']);
expect(member).to.have.all.keys(['_id', 'auth', 'flags', 'id', 'profile']);
expect(member.profile).to.have.all.keys(['name']);
});
});

View File

@@ -81,7 +81,7 @@ describe('GET /challenges/:challengeId/members/:memberId', () => {
await groupLeader.post(`/tasks/challenge/${challenge._id}`, [{type: 'habit', text: taskText}]);
let memberProgress = await user.get(`/challenges/${challenge._id}/members/${groupLeader._id}`);
expect(memberProgress).to.have.all.keys(['_id', 'id', 'profile', 'tasks']);
expect(memberProgress).to.have.all.keys(['_id', 'auth', 'flags', 'id', 'profile', 'tasks']);
expect(memberProgress.profile).to.have.all.keys(['name']);
expect(memberProgress.tasks.length).to.equal(1);
});

View File

@@ -39,6 +39,14 @@ describe('GET challenges/groups/:groupId', () => {
_id: publicGuild.leader._id,
id: publicGuild.leader._id,
profile: {name: user.profile.name},
auth: {
local: {
username: user.auth.local.username,
},
},
flags: {
verifiedUsername: true,
},
});
let foundChallenge2 = _.find(challenges, { _id: challenge2._id });
expect(foundChallenge2).to.exist;
@@ -46,6 +54,14 @@ describe('GET challenges/groups/:groupId', () => {
_id: publicGuild.leader._id,
id: publicGuild.leader._id,
profile: {name: user.profile.name},
auth: {
local: {
username: user.auth.local.username,
},
},
flags: {
verifiedUsername: true,
},
});
});
@@ -58,6 +74,14 @@ describe('GET challenges/groups/:groupId', () => {
_id: publicGuild.leader._id,
id: publicGuild.leader._id,
profile: {name: user.profile.name},
auth: {
local: {
username: user.auth.local.username,
},
},
flags: {
verifiedUsername: true,
},
});
let foundChallenge2 = _.find(challenges, { _id: challenge2._id });
expect(foundChallenge2).to.exist;
@@ -65,6 +89,14 @@ describe('GET challenges/groups/:groupId', () => {
_id: publicGuild.leader._id,
id: publicGuild.leader._id,
profile: {name: user.profile.name},
auth: {
local: {
username: user.auth.local.username,
},
},
flags: {
verifiedUsername: true,
},
});
});
@@ -125,6 +157,14 @@ describe('GET challenges/groups/:groupId', () => {
_id: privateGuild.leader._id,
id: privateGuild.leader._id,
profile: {name: user.profile.name},
auth: {
local: {
username: user.auth.local.username,
},
},
flags: {
verifiedUsername: true,
},
});
let foundChallenge2 = _.find(challenges, { _id: challenge2._id });
expect(foundChallenge2).to.exist;
@@ -132,6 +172,14 @@ describe('GET challenges/groups/:groupId', () => {
_id: privateGuild.leader._id,
id: privateGuild.leader._id,
profile: {name: user.profile.name},
auth: {
local: {
username: user.auth.local.username,
},
},
flags: {
verifiedUsername: true,
},
});
});
});
@@ -235,6 +283,14 @@ describe('GET challenges/groups/:groupId', () => {
_id: party.leader._id,
id: party.leader._id,
profile: {name: user.profile.name},
auth: {
local: {
username: user.auth.local.username,
},
},
flags: {
verifiedUsername: true,
},
});
let foundChallenge2 = _.find(challenges, { _id: challenge2._id });
expect(foundChallenge2).to.exist;
@@ -242,6 +298,14 @@ describe('GET challenges/groups/:groupId', () => {
_id: party.leader._id,
id: party.leader._id,
profile: {name: user.profile.name},
auth: {
local: {
username: user.auth.local.username,
},
},
flags: {
verifiedUsername: true,
},
});
});
@@ -254,6 +318,14 @@ describe('GET challenges/groups/:groupId', () => {
_id: party.leader._id,
id: party.leader._id,
profile: {name: user.profile.name},
auth: {
local: {
username: user.auth.local.username,
},
},
flags: {
verifiedUsername: true,
},
});
let foundChallenge2 = _.find(challenges, { _id: challenge2._id });
expect(foundChallenge2).to.exist;
@@ -261,6 +333,14 @@ describe('GET challenges/groups/:groupId', () => {
_id: party.leader._id,
id: party.leader._id,
profile: {name: user.profile.name},
auth: {
local: {
username: user.auth.local.username,
},
},
flags: {
verifiedUsername: true,
},
});
});
});
@@ -288,6 +368,14 @@ describe('GET challenges/groups/:groupId', () => {
_id: user._id,
id: user._id,
profile: {name: user.profile.name},
auth: {
local: {
username: user.auth.local.username,
},
},
flags: {
verifiedUsername: true,
},
});
let foundChallenge2 = _.find(challenges, { _id: challenge2._id });
expect(foundChallenge2).to.exist;
@@ -295,6 +383,14 @@ describe('GET challenges/groups/:groupId', () => {
_id: user._id,
id: user._id,
profile: {name: user.profile.name},
auth: {
local: {
username: user.auth.local.username,
},
},
flags: {
verifiedUsername: true,
},
});
});
@@ -307,6 +403,14 @@ describe('GET challenges/groups/:groupId', () => {
_id: user._id,
id: user._id,
profile: {name: user.profile.name},
auth: {
local: {
username: user.auth.local.username,
},
},
flags: {
verifiedUsername: true,
},
});
let foundChallenge2 = _.find(challenges, { _id: challenge2._id });
expect(foundChallenge2).to.exist;
@@ -314,6 +418,14 @@ describe('GET challenges/groups/:groupId', () => {
_id: user._id,
id: user._id,
profile: {name: user.profile.name},
auth: {
local: {
username: user.auth.local.username,
},
},
flags: {
verifiedUsername: true,
},
});
});
});

View File

@@ -40,6 +40,14 @@ describe('GET challenges/user', () => {
_id: publicGuild.leader._id,
id: publicGuild.leader._id,
profile: {name: user.profile.name},
auth: {
local: {
username: user.auth.local.username,
},
},
flags: {
verifiedUsername: true,
},
});
expect(foundChallenge.group).to.eql({
_id: publicGuild._id,
@@ -62,6 +70,14 @@ describe('GET challenges/user', () => {
_id: publicGuild.leader._id,
id: publicGuild.leader._id,
profile: {name: user.profile.name},
auth: {
local: {
username: user.auth.local.username,
},
},
flags: {
verifiedUsername: true,
},
});
expect(foundChallenge1.group).to.eql({
_id: publicGuild._id,
@@ -79,6 +95,14 @@ describe('GET challenges/user', () => {
_id: publicGuild.leader._id,
id: publicGuild.leader._id,
profile: {name: user.profile.name},
auth: {
local: {
username: user.auth.local.username,
},
},
flags: {
verifiedUsername: true,
},
});
expect(foundChallenge2.group).to.eql({
_id: publicGuild._id,
@@ -101,6 +125,14 @@ describe('GET challenges/user', () => {
_id: publicGuild.leader._id,
id: publicGuild.leader._id,
profile: {name: user.profile.name},
auth: {
local: {
username: user.auth.local.username,
},
},
flags: {
verifiedUsername: true,
},
});
expect(foundChallenge1.group).to.eql({
_id: publicGuild._id,
@@ -118,6 +150,14 @@ describe('GET challenges/user', () => {
_id: publicGuild.leader._id,
id: publicGuild.leader._id,
profile: {name: user.profile.name},
auth: {
local: {
username: user.auth.local.username,
},
},
flags: {
verifiedUsername: true,
},
});
expect(foundChallenge2.group).to.eql({
_id: publicGuild._id,

View File

@@ -79,6 +79,14 @@ describe('POST /challenges/:challengeId/join', () => {
_id: groupLeader._id,
id: groupLeader._id,
profile: {name: groupLeader.profile.name},
auth: {
local: {
username: groupLeader.auth.local.username,
},
},
flags: {
verifiedUsername: true,
},
});
expect(res.name).to.equal(challenge.name);
});

View File

@@ -79,6 +79,14 @@ describe('PUT /challenges/:challengeId', () => {
_id: member._id,
id: member._id,
profile: {name: member.profile.name},
auth: {
local: {
username: member.auth.local.username,
},
},
flags: {
verifiedUsername: true,
},
});
expect(res.name).to.equal('New Challenge Name');
expect(res.description).to.equal('New challenge description.');

View File

@@ -106,7 +106,7 @@ describe('POST /groups/:id/chat/:id/clearflags', () => {
.to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('messageCannotFlagSystemMessages', {communityManagerEmail: config.EMAILS.COMMUNITY_MANAGER_EMAIL}),
message: t('messageCannotFlagSystemMessages', {communityManagerEmail: config.EMAILS_COMMUNITY_MANAGER_EMAIL}),
});
// let messages = await members[0].get(`/groups/${group._id}/chat`);
// expect(messages[0].id).to.eql(skillMsg.id);

View File

@@ -203,7 +203,7 @@ describe('GET /groups', () => {
let page2 = await expect(user.get('/groups?type=publicGuilds&paginate=true&page=2'))
.to.eventually.have.a.lengthOf(1 + 4); // 1 created now, 4 by other tests
expect(page2[4].name).to.equal('guild with less members');
});
}).timeout(10000);
});
it('returns all the user\'s guilds when guilds passed in as query', async () => {

View File

@@ -50,6 +50,14 @@ describe('GET /groups/:groupId/invites', () => {
_id: invited._id,
id: invited._id,
profile: {name: invited.profile.name},
auth: {
local: {
username: invited.auth.local.username,
},
},
flags: {
verifiedUsername: true,
},
});
});
@@ -58,7 +66,7 @@ describe('GET /groups/:groupId/invites', () => {
let invited = await generateUser();
await user.post(`/groups/${group._id}/invite`, {uuids: [invited._id]});
let res = await user.get('/groups/party/invites');
expect(res[0]).to.have.all.keys(['_id', 'id', 'profile']);
expect(res[0]).to.have.all.keys(['_id', 'auth', 'flags', 'id', 'profile']);
expect(res[0].profile).to.have.all.keys(['name']);
});
@@ -76,10 +84,10 @@ describe('GET /groups/:groupId/invites', () => {
let res = await leader.get(`/groups/${group._id}/invites`);
expect(res.length).to.equal(30);
res.forEach(member => {
expect(member).to.have.all.keys(['_id', 'id', 'profile']);
expect(member).to.have.all.keys(['_id', 'auth', 'flags', 'id', 'profile']);
expect(member.profile).to.have.all.keys(['name']);
});
});
}).timeout(10000);
it('supports using req.query.lastId to get more invites', async function () {
this.timeout(30000); // @TODO: times out after 8 seconds

View File

@@ -56,13 +56,21 @@ describe('GET /groups/:groupId/members', () => {
_id: user._id,
id: user._id,
profile: {name: user.profile.name},
auth: {
local: {
username: user.auth.local.username,
},
},
flags: {
verifiedUsername: true,
},
});
});
it('populates only some fields', async () => {
await generateGroup(user, {type: 'party', name: generateUUID()});
let res = await user.get('/groups/party/members');
expect(res[0]).to.have.all.keys(['_id', 'id', 'profile']);
expect(res[0]).to.have.all.keys(['_id', 'auth', 'flags', 'id', 'profile']);
expect(res[0].profile).to.have.all.keys(['name']);
});
@@ -74,7 +82,7 @@ describe('GET /groups/:groupId/members', () => {
'_id', 'id', 'preferences', 'profile', 'stats', 'achievements', 'party',
'backer', 'contributor', 'auth', 'items', 'inbox', 'loginIncentives', 'flags',
]);
expect(Object.keys(memberRes.auth)).to.eql(['timestamps']);
expect(Object.keys(memberRes.auth)).to.eql(['local', 'timestamps']);
expect(Object.keys(memberRes.preferences).sort()).to.eql([
'size', 'hair', 'skin', 'shirt',
'chair', 'costume', 'sleep', 'background', 'tasks', 'disableClasses',
@@ -95,7 +103,7 @@ describe('GET /groups/:groupId/members', () => {
'_id', 'id', 'preferences', 'profile', 'stats', 'achievements', 'party',
'backer', 'contributor', 'auth', 'items', 'inbox', 'loginIncentives', 'flags',
]);
expect(Object.keys(memberRes.auth)).to.eql(['timestamps']);
expect(Object.keys(memberRes.auth)).to.eql(['local', 'timestamps']);
expect(Object.keys(memberRes.preferences).sort()).to.eql([
'size', 'hair', 'skin', 'shirt',
'chair', 'costume', 'sleep', 'background', 'tasks', 'disableClasses',
@@ -120,7 +128,7 @@ describe('GET /groups/:groupId/members', () => {
let res = await user.get('/groups/party/members');
expect(res.length).to.equal(30);
res.forEach(member => {
expect(member).to.have.all.keys(['_id', 'id', 'profile']);
expect(member).to.have.all.keys(['_id', 'auth', 'flags', 'id', 'profile']);
expect(member.profile).to.have.all.keys(['name']);
});
});
@@ -137,7 +145,7 @@ describe('GET /groups/:groupId/members', () => {
let res = await user.get('/groups/party/members?includeAllMembers=true');
expect(res.length).to.equal(30);
res.forEach(member => {
expect(member).to.have.all.keys(['_id', 'id', 'profile']);
expect(member).to.have.all.keys(['_id', 'auth', 'flags', 'id', 'profile']);
expect(member.profile).to.have.all.keys(['name']);
});
});

View File

@@ -23,6 +23,73 @@ describe('Post /groups/:groupId/invite', () => {
});
});
describe('username invites', () => {
it('returns an error when invited user is not found', async () => {
const fakeID = 'fakeuserid';
await expect(inviter.post(`/groups/${group._id}/invite`, {
usernames: [fakeID],
}))
.to.eventually.be.rejected.and.eql({
code: 404,
error: 'NotFound',
message: t('userWithUsernameNotFound', {username: fakeID}),
});
});
it('returns an error when inviting yourself to a group', async () => {
await expect(inviter.post(`/groups/${group._id}/invite`, {
usernames: [inviter.auth.local.lowerCaseUsername],
}))
.to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('cannotInviteSelfToGroup'),
});
});
it('invites a user to a group by username', async () => {
const userToInvite = await generateUser();
await expect(inviter.post(`/groups/${group._id}/invite`, {
usernames: [userToInvite.auth.local.lowerCaseUsername],
})).to.eventually.deep.equal([{
id: group._id,
name: groupName,
inviter: inviter._id,
publicGuild: false,
}]);
await expect(userToInvite.get('/user'))
.to.eventually.have.nested.property('invitations.guilds[0].id', group._id);
});
it('invites multiple users to a group by uuid', async () => {
const userToInvite = await generateUser();
const userToInvite2 = await generateUser();
await expect(inviter.post(`/groups/${group._id}/invite`, {
usernames: [userToInvite.auth.local.lowerCaseUsername, userToInvite2.auth.local.lowerCaseUsername],
})).to.eventually.deep.equal([
{
id: group._id,
name: groupName,
inviter: inviter._id,
publicGuild: false,
},
{
id: group._id,
name: groupName,
inviter: inviter._id,
publicGuild: false,
},
]);
await expect(userToInvite.get('/user')).to.eventually.have.nested.property('invitations.guilds[0].id', group._id);
await expect(userToInvite2.get('/user')).to.eventually.have.nested.property('invitations.guilds[0].id', group._id);
});
});
describe('user id invites', () => {
it('returns an error when inviter has no chat privileges', async () => {
let inviterMuted = await inviter.update({'flags.chatRevoked': true});
@@ -93,7 +160,7 @@ describe('Post /groups/:groupId/invite', () => {
.to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('inviteMissingUuid'),
message: t('inviteMustNotBeEmpty'),
});
});
@@ -228,7 +295,7 @@ describe('Post /groups/:groupId/invite', () => {
.to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('inviteMissingEmail'),
message: t('inviteMustNotBeEmpty'),
});
});
@@ -266,7 +333,7 @@ describe('Post /groups/:groupId/invite', () => {
.to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: t('inviteLimitReached', {techAssistanceEmail: nconf.get('EMAILS:TECH_ASSISTANCE_EMAIL')}),
message: t('inviteLimitReached', {techAssistanceEmail: nconf.get('EMAILS_TECH_ASSISTANCE_EMAIL')}),
});
});
@@ -417,7 +484,7 @@ describe('Post /groups/:groupId/invite', () => {
expect(await inviter.post(`/groups/${group._id}/invite`, {
uuids: generatedInvites.map(invite => invite._id),
})).to.be.an('array');
});
}).timeout(10000);
// @TODO: Add this after we are able to mock the group plan route
xit('returns an error when a non-leader invites to a group plan', async () => {
@@ -564,7 +631,7 @@ describe('Post /groups/:groupId/invite', () => {
expect(await inviter.post(`/groups/${party._id}/invite`, {
uuids: generatedInvites.map(invite => invite._id),
})).to.be.an('array');
});
}).timeout(10000);
it('does not allow 30+ members in a party', async () => {
let invitesToGenerate = [];
@@ -582,6 +649,6 @@ describe('Post /groups/:groupId/invite', () => {
error: 'BadRequest',
message: t('partyExceedsMembersLimit', {maxMembersParty: PARTY_LIMIT_MEMBERS}),
});
});
}).timeout(10000);
});
});

View File

@@ -56,5 +56,5 @@ describe('GET /hall/patrons', () => {
expect(morePatrons.length).to.equal(2);
expect(morePatrons[0].backer.tier).to.equal(2);
expect(morePatrons[1].backer.tier).to.equal(1);
});
}).timeout(10000);
});

View File

@@ -34,7 +34,7 @@ describe('GET /members/:memberId', () => {
'_id', 'id', 'preferences', 'profile', 'stats', 'achievements', 'party',
'backer', 'contributor', 'auth', 'items', 'inbox', 'loginIncentives', 'flags',
]);
expect(Object.keys(memberRes.auth)).to.eql(['timestamps']);
expect(Object.keys(memberRes.auth)).to.eql(['local', 'timestamps']);
expect(Object.keys(memberRes.preferences).sort()).to.eql([
'size', 'hair', 'skin', 'shirt',
'chair', 'costume', 'sleep', 'background', 'tasks', 'disableClasses',

View File

@@ -23,7 +23,7 @@ describe('payments : amazon #subscribeCancel', () => {
describe('success', () => {
beforeEach(() => {
amazonSubscribeCancelStub = sinon.stub(amzLib, 'cancelSubscription').returnsPromise().resolves({});
amazonSubscribeCancelStub = sinon.stub(amzLib, 'cancelSubscription').resolves({});
});
afterEach(() => {

View File

@@ -21,7 +21,7 @@ describe('payments - amazon - #checkout', () => {
describe('success', () => {
beforeEach(async () => {
amazonCheckoutStub = sinon.stub(amzLib, 'checkout').returnsPromise().resolves({});
amazonCheckoutStub = sinon.stub(amzLib, 'checkout').resolves({});
});
afterEach(() => {

View File

@@ -27,7 +27,7 @@ describe('payments - amazon - #subscribe', () => {
let coupon;
beforeEach(() => {
subscribeWithAmazonStub = sinon.stub(amzLib, 'subscribe').returnsPromise().resolves({});
subscribeWithAmazonStub = sinon.stub(amzLib, 'subscribe').resolves({});
});
afterEach(() => {

View File

@@ -13,7 +13,7 @@ describe('payments : apple #cancelSubscribe', () => {
let cancelStub;
beforeEach(async () => {
cancelStub = sinon.stub(applePayments, 'cancelSubscribe').returnsPromise().resolves({});
cancelStub = sinon.stub(applePayments, 'cancelSubscribe').resolves({});
});
afterEach(() => {

View File

@@ -13,7 +13,7 @@ describe('payments : apple #verify', () => {
let verifyStub;
beforeEach(async () => {
verifyStub = sinon.stub(applePayments, 'verifyGemPurchase').returnsPromise().resolves({});
verifyStub = sinon.stub(applePayments, 'verifyGemPurchase').resolves({});
});
afterEach(() => {

View File

@@ -21,7 +21,7 @@ describe('payments : apple #subscribe', () => {
let subscribeStub;
beforeEach(async () => {
subscribeStub = sinon.stub(applePayments, 'subscribe').returnsPromise().resolves({});
subscribeStub = sinon.stub(applePayments, 'subscribe').resolves({});
});
afterEach(() => {

View File

@@ -13,7 +13,7 @@ describe('payments : google #cancelSubscribe', () => {
let cancelStub;
beforeEach(async () => {
cancelStub = sinon.stub(googlePayments, 'cancelSubscribe').returnsPromise().resolves({});
cancelStub = sinon.stub(googlePayments, 'cancelSubscribe').resolves({});
});
afterEach(() => {

View File

@@ -21,7 +21,7 @@ describe('payments : google #subscribe', () => {
let subscribeStub;
beforeEach(async () => {
subscribeStub = sinon.stub(googlePayments, 'subscribe').returnsPromise().resolves({});
subscribeStub = sinon.stub(googlePayments, 'subscribe').resolves({});
});
afterEach(() => {

View File

@@ -13,7 +13,7 @@ describe('payments : google #verify', () => {
let verifyStub;
beforeEach(async () => {
verifyStub = sinon.stub(googlePayments, 'verifyGemPurchase').returnsPromise().resolves({});
verifyStub = sinon.stub(googlePayments, 'verifyGemPurchase').resolves({});
});
afterEach(() => {

View File

@@ -15,7 +15,7 @@ describe('payments : paypal #checkout', () => {
let checkoutStub;
beforeEach(async () => {
checkoutStub = sinon.stub(paypalPayments, 'checkout').returnsPromise().resolves('/');
checkoutStub = sinon.stub(paypalPayments, 'checkout').resolves('/');
});
afterEach(() => {

View File

@@ -34,7 +34,7 @@ describe('payments : paypal #checkoutSuccess', () => {
let checkoutSuccessStub;
beforeEach(async () => {
checkoutSuccessStub = sinon.stub(paypalPayments, 'checkoutSuccess').returnsPromise().resolves({});
checkoutSuccessStub = sinon.stub(paypalPayments, 'checkoutSuccess').resolves({});
});
afterEach(() => {

View File

@@ -25,7 +25,7 @@ describe('payments : paypal #subscribe', () => {
let subscribeStub;
beforeEach(async () => {
subscribeStub = sinon.stub(paypalPayments, 'subscribe').returnsPromise().resolves('/');
subscribeStub = sinon.stub(paypalPayments, 'subscribe').resolves('/');
});
afterEach(() => {

View File

@@ -25,7 +25,7 @@ describe('payments : paypal #subscribeCancel', () => {
let subscribeCancelStub;
beforeEach(async () => {
subscribeCancelStub = sinon.stub(paypalPayments, 'subscribeCancel').returnsPromise().resolves('/');
subscribeCancelStub = sinon.stub(paypalPayments, 'subscribeCancel').resolves('/');
});
afterEach(() => {

View File

@@ -24,7 +24,7 @@ describe('payments : paypal #subscribeSuccess', () => {
let subscribeSuccessStub;
beforeEach(async () => {
subscribeSuccessStub = sinon.stub(paypalPayments, 'subscribeSuccess').returnsPromise().resolves({});
subscribeSuccessStub = sinon.stub(paypalPayments, 'subscribeSuccess').resolves({});
});
afterEach(() => {

View File

@@ -20,7 +20,7 @@ describe('payments - paypal - #ipn', () => {
let ipnStub;
beforeEach(async () => {
ipnStub = sinon.stub(paypalPayments, 'ipn').returnsPromise().resolves({});
ipnStub = sinon.stub(paypalPayments, 'ipn').resolves({});
});
afterEach(() => {

View File

@@ -23,7 +23,7 @@ describe('payments - stripe - #subscribeCancel', () => {
describe('success', () => {
beforeEach(async () => {
stripeCancelSubscriptionStub = sinon.stub(stripePayments, 'cancelSubscription').returnsPromise().resolves({});
stripeCancelSubscriptionStub = sinon.stub(stripePayments, 'cancelSubscription').resolves({});
});
afterEach(() => {

View File

@@ -24,7 +24,7 @@ describe('payments - stripe - #checkout', () => {
let stripeCheckoutSubscriptionStub;
beforeEach(async () => {
stripeCheckoutSubscriptionStub = sinon.stub(stripePayments, 'checkout').returnsPromise().resolves({});
stripeCheckoutSubscriptionStub = sinon.stub(stripePayments, 'checkout').resolves({});
});
afterEach(() => {

View File

@@ -25,7 +25,7 @@ describe('payments - stripe - #subscribeEdit', () => {
let stripeEditSubscriptionStub;
beforeEach(async () => {
stripeEditSubscriptionStub = sinon.stub(stripePayments, 'editSubscription').returnsPromise().resolves({});
stripeEditSubscriptionStub = sinon.stub(stripePayments, 'editSubscription').resolves({});
});
afterEach(() => {

View File

@@ -20,8 +20,8 @@ describe('DELETE user message', () => {
messagesId = Object.keys(userRes.inbox.messages);
expect(messagesId.length).to.eql(2);
expect(userRes.inbox.messages[messagesId[0]].text).to.eql('first');
expect(userRes.inbox.messages[messagesId[1]].text).to.eql('second');
expect(userRes.inbox.messages[messagesId[0]].text).to.eql('second');
expect(userRes.inbox.messages[messagesId[1]].text).to.eql('first');
});
it('one message', async () => {
@@ -31,7 +31,7 @@ describe('DELETE user message', () => {
let userRes = await user.get('/user');
expect(Object.keys(userRes.inbox.messages).length).to.eql(1);
expect(userRes.inbox.messages[messagesId[0]].text).to.eql('second');
expect(userRes.inbox.messages[messagesId[0]].text).to.eql('first');
});
it('clear all', async () => {

View File

@@ -18,7 +18,12 @@ describe('GET /user/anonymized', () => {
'profile.name': 'profile',
'purchased.plan': 'purchased plan',
contributor: 'contributor',
invitations: 'invitations',
invitations: {
guilds: ['guild1', 'guild2'],
party: {
_id: 'partyid',
},
},
'items.special.nyeReceived': 'some',
'items.special.valentineReceived': 'some',
webhooks: [{url: 'https://somurl.com'}],

View File

@@ -39,14 +39,16 @@ describe('POST /user/push-devices', () => {
});
});
it('returns an error if user already has the push device', async () => {
it('fails silently if user already has the push device', async () => {
await user.post('/user/push-devices', {type, regId});
await expect(user.post('/user/push-devices', {type, regId}))
.to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: t('pushDeviceAlreadyAdded'),
});
const response = await user.post('/user/push-devices', {type, regId});
await user.sync();
expect(response.message).to.equal(t('pushDeviceAdded'));
expect(response.data[0].type).to.equal(type);
expect(response.data[0].regId).to.equal(regId);
expect(user.pushDevices[0].type).to.equal(type);
expect(user.pushDevices[0].regId).to.equal(regId);
});
it('adds a push device to the user', async () => {

View File

@@ -94,9 +94,6 @@ describe('POST /user/auth/reset-password-set-new-one', () => {
userId: user._id,
expiresAt: moment().add({days: 1}),
}));
await user.update({
auth: 'not an object with valid fields',
});
await expect(api.post(`${endpoint}`, {
code,

View File

@@ -45,7 +45,7 @@ describe('POST /user/auth/local/login', () => {
})).to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: t('accountSuspended', { communityManagerEmail: nconf.get('EMAILS:COMMUNITY_MANAGER_EMAIL'), userId: user._id }),
message: t('accountSuspended', { communityManagerEmail: nconf.get('EMAILS_COMMUNITY_MANAGER_EMAIL'), userId: user._id }),
});
});

View File

@@ -276,7 +276,7 @@ describe('POST /user/auth/local/register', () => {
});
});
it('enrolls new users in an A/B test', async () => {
xit('enrolls new users in an A/B test', async () => {
let username = generateRandomUserName();
let email = `${username}@example.com`;
let password = 'password';

View File

@@ -1,102 +0,0 @@
/* eslint-disable camelcase */
import {
generateUser,
requester,
translate as t,
} from '../../../../../helpers/api-integration/v3';
import { v4 as generateUUID } from 'uuid';
describe('POST /user/auth/pusher', () => {
let user;
let endpoint = '/user/auth/pusher';
beforeEach(async () => {
user = await generateUser();
});
it('requires authentication', async () => {
let api = requester();
await expect(api.post(endpoint)).to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: t('missingAuthHeaders'),
});
});
it('returns an error if req.body.socket_id is missing', async () => {
await expect(user.post(endpoint, {
channel_name: '123',
})).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('invalidReqParams'),
});
});
it('returns an error if req.body.channel_name is missing', async () => {
await expect(user.post(endpoint, {
socket_id: '123',
})).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('invalidReqParams'),
});
});
it('returns an error if req.body.channel_name is badly formatted', async () => {
await expect(user.post(endpoint, {
channel_name: '123',
socket_id: '123',
})).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: 'Invalid Pusher channel type.',
});
});
it('returns an error if an invalid channel type is passed', async () => {
await expect(user.post(endpoint, {
channel_name: 'invalid-group-123',
socket_id: '123',
})).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: 'Invalid Pusher channel type.',
});
});
it('returns an error if an invalid resource type is passed', async () => {
await expect(user.post(endpoint, {
channel_name: 'presence-user-123',
socket_id: '123',
})).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: 'Invalid Pusher resource type.',
});
});
it('returns an error if an invalid resource id is passed', async () => {
await expect(user.post(endpoint, {
channel_name: 'presence-group-123',
socket_id: '123',
})).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: 'Invalid Pusher resource id, must be a UUID.',
});
});
it('returns an error if the passed resource id doesn\'t match the user\'s party', async () => {
await expect(user.post(endpoint, {
channel_name: `presence-group-${generateUUID()}`,
socket_id: '123',
})).to.eventually.be.rejected.and.eql({
code: 404,
error: 'NotFound',
message: 'Resource id must be the user\'s party.',
});
});
});

View File

@@ -80,7 +80,7 @@ describe('POST /user/auth/social', () => {
expect(response.newUser).to.be.false;
});
it('enrolls a new user in an A/B test', async () => {
xit('enrolls a new user in an A/B test', async () => {
await api.post(endpoint, {
authResponse: {access_token: randomAccessToken}, // eslint-disable-line camelcase
network,
@@ -136,7 +136,7 @@ describe('POST /user/auth/social', () => {
expect(response.newUser).to.be.false;
});
it('enrolls a new user in an A/B test', async () => {
xit('enrolls a new user in an A/B test', async () => {
await api.post(endpoint, {
authResponse: {access_token: randomAccessToken}, // eslint-disable-line camelcase
network,

View File

@@ -71,7 +71,7 @@ describe('PUT /user/auth/update-email', () => {
})).to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: t('cannotFulfillReq', { techAssistanceEmail: nconf.get('EMAILS:TECH_ASSISTANCE_EMAIL') }),
message: t('cannotFulfillReq', { techAssistanceEmail: nconf.get('EMAILS_TECH_ASSISTANCE_EMAIL') }),
});
});

View File

@@ -12,14 +12,14 @@ const ENDPOINT = '/user/auth/update-username';
describe('PUT /user/auth/update-username', async () => {
let user;
let newUsername = 'new-username';
let password = 'password'; // from habitrpg/test/helpers/api-integration/v3/object-generators.js
let password = 'password'; // from habitrpg/test/helpers/api-integration/v4/object-generators.js
beforeEach(async () => {
user = await generateUser();
});
it('successfully changes username', async () => {
it('successfully changes username with password', async () => {
let newUsername = 'new-username';
let response = await user.put(ENDPOINT, {
username: newUsername,
password,
@@ -29,6 +29,38 @@ describe('PUT /user/auth/update-username', async () => {
expect(user.auth.local.username).to.eql(newUsername);
});
it('successfully changes username without password', async () => {
let newUsername = 'new-username-nopw';
let response = await user.put(ENDPOINT, {
username: newUsername,
});
expect(response).to.eql({ username: newUsername });
await user.sync();
expect(user.auth.local.username).to.eql(newUsername);
});
it('successfully changes username containing number and underscore', async () => {
let newUsername = 'new_username9';
let response = await user.put(ENDPOINT, {
username: newUsername,
});
expect(response).to.eql({ username: newUsername });
await user.sync();
expect(user.auth.local.username).to.eql(newUsername);
});
it('sets verifiedUsername when changing username', async () => {
user.flags.verifiedUsername = false;
await user.sync();
let newUsername = 'new-username-verify';
let response = await user.put(ENDPOINT, {
username: newUsername,
});
expect(response).to.eql({ username: newUsername });
await user.sync();
expect(user.flags.verifiedUsername).to.eql(true);
});
it('converts user with SHA1 encrypted password to bcrypt encryption', async () => {
let myNewUsername = 'my-new-username';
let textPassword = 'mySecretPassword';
@@ -80,6 +112,7 @@ describe('PUT /user/auth/update-username', async () => {
});
it('errors if password is wrong', async () => {
let newUsername = 'new-username';
await expect(user.put(ENDPOINT, {
username: newUsername,
password: 'wrong-password',
@@ -90,19 +123,6 @@ describe('PUT /user/auth/update-username', async () => {
});
});
it('prevents social-only user from changing username', async () => {
let socialUser = await generateUser({ 'auth.local': { ok: true } });
await expect(socialUser.put(ENDPOINT, {
username: newUsername,
password,
})).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('userHasNoLocalRegistration'),
});
});
it('errors if new username is not provided', async () => {
await expect(user.put(ENDPOINT, {
password,
@@ -112,5 +132,93 @@ describe('PUT /user/auth/update-username', async () => {
message: t('invalidReqParams'),
});
});
it('errors if new username is a slur', async () => {
await expect(user.put(ENDPOINT, {
username: 'TESTPLACEHOLDERSLURWORDHERE',
})).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: [t('usernameIssueLength'), t('usernameIssueSlur')].join(' '),
});
});
it('errors if new username contains a slur', async () => {
await expect(user.put(ENDPOINT, {
username: 'TESTPLACEHOLDERSLURWORDHERE_otherword',
})).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: [t('usernameIssueLength'), t('usernameIssueSlur')].join(' '),
});
await expect(user.put(ENDPOINT, {
username: 'something_TESTPLACEHOLDERSLURWORDHERE',
})).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: [t('usernameIssueLength'), t('usernameIssueSlur')].join(' '),
});
await expect(user.put(ENDPOINT, {
username: 'somethingTESTPLACEHOLDERSLURWORDHEREotherword',
})).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: [t('usernameIssueLength'), t('usernameIssueSlur')].join(' '),
});
});
it('errors if new username is not allowed', async () => {
await expect(user.put(ENDPOINT, {
username: 'support',
})).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('usernameIssueForbidden'),
});
});
it('errors if new username is not allowed regardless of casing', async () => {
await expect(user.put(ENDPOINT, {
username: 'SUppORT',
})).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('usernameIssueForbidden'),
});
});
it('errors if username has incorrect length', async () => {
await expect(user.put(ENDPOINT, {
username: 'thisisaverylongusernameover20characters',
})).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('usernameIssueLength'),
});
});
it('errors if new username contains invalid characters', async () => {
await expect(user.put(ENDPOINT, {
username: 'Eichhörnchen',
})).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('usernameIssueInvalidCharacters'),
});
await expect(user.put(ENDPOINT, {
username: 'test.name',
})).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('usernameIssueInvalidCharacters'),
});
await expect(user.put(ENDPOINT, {
username: '🤬',
})).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('usernameIssueInvalidCharacters'),
});
});
});
});

View File

@@ -53,4 +53,15 @@ describe('POST /user/buy-gear/:key', () => {
message: 'You need to purchase a lower level gear before this one.',
});
});
it('returns an error if tries to buy gear from a different class', async () => {
let key = 'armor_rogue_1';
return expect(user.post(`/user/buy-gear/${key}`))
.to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: 'You can\'t buy this item.',
});
});
});

View File

@@ -53,6 +53,6 @@ describe('GET /user', () => {
let returnedUser = await user.get('/user');
expect(returnedUser._id).to.equal(user._id);
expect(returnedUser.inbox.messages).to.be.empty;
expect(returnedUser.inbox.messages).to.be.undefined;
});
});

View File

@@ -0,0 +1,57 @@
import {
generateUser,
translate as t,
} from '../../../../helpers/api-integration/v4';
const ENDPOINT = '/user/auth/verify-display-name';
describe('POST /user/auth/verify-display-name', async () => {
let user;
beforeEach(async () => {
user = await generateUser();
});
it('successfully verifies display name including funky characters', async () => {
let newDisplayName = 'Sabé 🤬';
let response = await user.post(ENDPOINT, {
displayName: newDisplayName,
});
expect(response).to.eql({ isUsable: true });
});
context('errors', async () => {
it('errors if display name is not provided', async () => {
await expect(user.post(ENDPOINT, {
})).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('invalidReqParams'),
});
});
it('errors if display name is a slur', async () => {
await expect(user.post(ENDPOINT, {
displayName: 'TESTPLACEHOLDERSLURWORDHERE',
})).to.eventually.eql({ isUsable: false, issues: [t('displaynameIssueSlur')] });
});
it('errors if display name contains a slur', async () => {
await expect(user.post(ENDPOINT, {
displayName: 'TESTPLACEHOLDERSLURWORDHERE_otherword',
})).to.eventually.eql({ isUsable: false, issues: [t('displaynameIssueLength'), t('displaynameIssueSlur')] });
await expect(user.post(ENDPOINT, {
displayName: 'something_TESTPLACEHOLDERSLURWORDHERE',
})).to.eventually.eql({ isUsable: false, issues: [t('displaynameIssueLength'), t('displaynameIssueSlur')] });
await expect(user.post(ENDPOINT, {
displayName: 'somethingTESTPLACEHOLDERSLURWORDHEREotherword',
})).to.eventually.eql({ isUsable: false, issues: [t('displaynameIssueLength'), t('displaynameIssueSlur')] });
});
it('errors if display name has incorrect length', async () => {
await expect(user.post(ENDPOINT, {
displayName: 'this is a very long display name over 30 characters',
})).to.eventually.eql({ isUsable: false, issues: [t('displaynameIssueLength')] });
});
});
});

View File

@@ -1,224 +0,0 @@
import {
generateUser,
translate as t,
} from '../../../../helpers/api-integration/v4';
import {
bcryptCompare,
sha1MakeSalt,
sha1Encrypt as sha1EncryptPassword,
} from '../../../../../website/server/libs/password';
const ENDPOINT = '/user/auth/update-username';
describe('PUT /user/auth/update-username', async () => {
let user;
let password = 'password'; // from habitrpg/test/helpers/api-integration/v4/object-generators.js
beforeEach(async () => {
user = await generateUser();
});
it('successfully changes username with password', async () => {
let newUsername = 'new-username';
let response = await user.put(ENDPOINT, {
username: newUsername,
password,
});
expect(response).to.eql({ username: newUsername });
await user.sync();
expect(user.auth.local.username).to.eql(newUsername);
});
it('successfully changes username without password', async () => {
let newUsername = 'new-username-nopw';
let response = await user.put(ENDPOINT, {
username: newUsername,
});
expect(response).to.eql({ username: newUsername });
await user.sync();
expect(user.auth.local.username).to.eql(newUsername);
});
it('successfully changes username containing number and underscore', async () => {
let newUsername = 'new_username9';
let response = await user.put(ENDPOINT, {
username: newUsername,
});
expect(response).to.eql({ username: newUsername });
await user.sync();
expect(user.auth.local.username).to.eql(newUsername);
});
it('sets verifiedUsername when changing username', async () => {
user.flags.verifiedUsername = false;
await user.sync();
let newUsername = 'new-username-verify';
let response = await user.put(ENDPOINT, {
username: newUsername,
});
expect(response).to.eql({ username: newUsername });
await user.sync();
expect(user.flags.verifiedUsername).to.eql(true);
});
it('converts user with SHA1 encrypted password to bcrypt encryption', async () => {
let myNewUsername = 'my-new-username';
let textPassword = 'mySecretPassword';
let salt = sha1MakeSalt();
let sha1HashedPassword = sha1EncryptPassword(textPassword, salt);
await user.update({
'auth.local.hashed_password': sha1HashedPassword,
'auth.local.passwordHashMethod': 'sha1',
'auth.local.salt': salt,
});
await user.sync();
expect(user.auth.local.passwordHashMethod).to.equal('sha1');
expect(user.auth.local.salt).to.equal(salt);
expect(user.auth.local.hashed_password).to.equal(sha1HashedPassword);
// update email
let response = await user.put(ENDPOINT, {
username: myNewUsername,
password: textPassword,
});
expect(response).to.eql({ username: myNewUsername });
await user.sync();
expect(user.auth.local.username).to.eql(myNewUsername);
expect(user.auth.local.passwordHashMethod).to.equal('bcrypt');
expect(user.auth.local.salt).to.be.undefined;
expect(user.auth.local.hashed_password).not.to.equal(sha1HashedPassword);
let isValidPassword = await bcryptCompare(textPassword, user.auth.local.hashed_password);
expect(isValidPassword).to.equal(true);
});
context('errors', async () => {
it('prevents username update if new username is already taken', async () => {
let existingUsername = 'existing-username';
await generateUser({'auth.local.username': existingUsername, 'auth.local.lowerCaseUsername': existingUsername });
await expect(user.put(ENDPOINT, {
username: existingUsername,
password,
})).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('usernameTaken'),
});
});
it('errors if password is wrong', async () => {
let newUsername = 'new-username';
await expect(user.put(ENDPOINT, {
username: newUsername,
password: 'wrong-password',
})).to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: t('wrongPassword'),
});
});
it('errors if new username is not provided', async () => {
await expect(user.put(ENDPOINT, {
password,
})).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('invalidReqParams'),
});
});
it('errors if new username is a slur', async () => {
await expect(user.put(ENDPOINT, {
username: 'TESTPLACEHOLDERSLURWORDHERE',
})).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: [t('usernameIssueLength'), t('usernameIssueSlur')].join(' '),
});
});
it('errors if new username contains a slur', async () => {
await expect(user.put(ENDPOINT, {
username: 'TESTPLACEHOLDERSLURWORDHERE_otherword',
})).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: [t('usernameIssueLength'), t('usernameIssueSlur')].join(' '),
});
await expect(user.put(ENDPOINT, {
username: 'something_TESTPLACEHOLDERSLURWORDHERE',
})).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: [t('usernameIssueLength'), t('usernameIssueSlur')].join(' '),
});
await expect(user.put(ENDPOINT, {
username: 'somethingTESTPLACEHOLDERSLURWORDHEREotherword',
})).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: [t('usernameIssueLength'), t('usernameIssueSlur')].join(' '),
});
});
it('errors if new username is not allowed', async () => {
await expect(user.put(ENDPOINT, {
username: 'support',
})).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('usernameIssueForbidden'),
});
});
it('errors if new username is not allowed regardless of casing', async () => {
await expect(user.put(ENDPOINT, {
username: 'SUppORT',
})).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('usernameIssueForbidden'),
});
});
it('errors if username has incorrect length', async () => {
await expect(user.put(ENDPOINT, {
username: 'thisisaverylongusernameover20characters',
})).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('usernameIssueLength'),
});
});
it('errors if new username contains invalid characters', async () => {
await expect(user.put(ENDPOINT, {
username: 'Eichhörnchen',
})).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('usernameIssueInvalidCharacters'),
});
await expect(user.put(ENDPOINT, {
username: 'test.name',
})).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('usernameIssueInvalidCharacters'),
});
await expect(user.put(ENDPOINT, {
username: '🤬',
})).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('usernameIssueInvalidCharacters'),
});
});
});
});

View File

@@ -4,7 +4,7 @@ require('babel-polyfill');
// Automatically setup SinonJS' sandbox for each test
beforeEach(() => {
global.sandbox = sinon.sandbox.create();
global.sandbox = sinon.createSandbox();
});
afterEach(() => {

View File

@@ -2,12 +2,14 @@ import { shallowMount, createLocalVue } from '@vue/test-utils';
import NotificationsComponent from 'client/components/notifications.vue';
import Store from 'client/libs/store';
import { hasClass } from 'client/store/getters/members';
import { toNextLevel } from 'common/script/statHelpers';
const localVue = createLocalVue();
localVue.use(Store);
describe('Notifications', () => {
let store;
let wrapper;
beforeEach(() => {
store = new Store({
@@ -29,16 +31,22 @@ describe('Notifications', () => {
actions: {
'user:fetch': () => {},
'tasks:fetchUserTasks': () => {},
'snackbars:add': () => {},
},
getters: {
'members:hasClass': hasClass,
},
});
wrapper = shallowMount(NotificationsComponent, {
store,
localVue,
mocks: {
$t: (string) => string,
},
});
});
it('set user has class computed prop', () => {
const wrapper = shallowMount(NotificationsComponent, { store, localVue });
expect(wrapper.vm.userHasClass).to.be.false;
store.state.user.data.stats.lvl = 10;
@@ -47,4 +55,130 @@ describe('Notifications', () => {
expect(wrapper.vm.userHasClass).to.be.true;
});
describe('user exp notifcation', () => {
it('notifies when user gets more exp', () => {
const expSpy = sinon.spy(wrapper.vm, 'exp');
const userLevel = 10;
store.state.user.data.stats.lvl = userLevel;
const userExpBefore = 10;
const userExpAfter = 12;
wrapper.vm.displayUserExpAndLvlNotifications(userExpAfter, userExpBefore, userLevel, userLevel);
expect(expSpy).to.be.calledWith(userExpAfter - userExpBefore);
expSpy.restore();
});
it('when user levels with exact xp', () => {
const expSpy = sinon.spy(wrapper.vm, 'exp');
const userLevelBefore = 9;
const userLevelAfter = 10;
store.state.user.data.stats.lvl = userLevelAfter;
const expEarned = 5;
const userExpBefore = toNextLevel(userLevelBefore) - expEarned;
const userExpAfter = 0;
wrapper.vm.displayUserExpAndLvlNotifications(userExpAfter, userExpBefore, userLevelAfter, userLevelBefore);
expect(expSpy).to.be.calledWith(expEarned);
expSpy.restore();
});
it('when user levels with exact more exp than needed', () => {
const expSpy = sinon.spy(wrapper.vm, 'exp');
const userLevelBefore = 9;
const userLevelAfter = 10;
store.state.user.data.stats.lvl = userLevelAfter;
const expEarned = 10;
const expNeeded = 5;
const userExpBefore = toNextLevel(userLevelBefore) - expNeeded;
const userExpAfter = 5;
wrapper.vm.displayUserExpAndLvlNotifications(userExpAfter, userExpBefore, userLevelAfter, userLevelBefore);
expect(expSpy).to.be.calledWith(expEarned);
expSpy.restore();
});
it('when user has more exp than needed then levels', () => {
const expSpy = sinon.spy(wrapper.vm, 'exp');
const userLevelBefore = 9;
const userLevelAfter = 10;
store.state.user.data.stats.lvl = userLevelAfter;
const expEarned = 10;
const expNeeded = -5;
const userExpBefore = toNextLevel(userLevelBefore) - expNeeded;
const userExpAfter = 15;
wrapper.vm.displayUserExpAndLvlNotifications(userExpAfter, userExpBefore, userLevelAfter, userLevelBefore);
expect(expSpy).to.be.calledWith(expEarned);
expSpy.restore();
});
it('when user multilevels', () => {
const expSpy = sinon.spy(wrapper.vm, 'exp');
const userLevelBefore = 8;
const userLevelAfter = 10;
store.state.user.data.stats.lvl = userLevelAfter;
const expEarned = 10 + toNextLevel(userLevelBefore + 1);
const expNeeded = 5;
const userExpBefore = toNextLevel(userLevelBefore) - expNeeded;
const userExpAfter = 5;
wrapper.vm.displayUserExpAndLvlNotifications(userExpAfter, userExpBefore, userLevelAfter, userLevelBefore);
expect(expSpy).to.be.calledWith(expEarned);
expSpy.restore();
});
it('when user looses xp', () => {
const expSpy = sinon.spy(wrapper.vm, 'exp');
const userLevel = 10;
store.state.user.data.stats.lvl = userLevel;
const userExpBefore = 10;
const userExpAfter = 5;
wrapper.vm.displayUserExpAndLvlNotifications(userExpAfter, userExpBefore, userLevel, userLevel);
expect(expSpy).to.be.calledWith(userExpAfter - userExpBefore);
expSpy.restore();
});
it('when user looses xp under 0', () => {
const expSpy = sinon.spy(wrapper.vm, 'exp');
const userLevel = 10;
store.state.user.data.stats.lvl = userLevel;
const userExpBefore = 5;
const userExpAfter = -3;
wrapper.vm.displayUserExpAndLvlNotifications(userExpAfter, userExpBefore, userLevel, userLevel);
expect(expSpy).to.be.calledWith(userExpAfter - userExpBefore);
expSpy.restore();
});
it('when user dies', () => {
const expSpy = sinon.spy(wrapper.vm, 'exp');
const userLevelBefore = 10;
const userLevelAfter = 9;
store.state.user.data.stats.lvl = userLevelAfter;
const expEarned = -20;
const userExpBefore = 20;
const userExpAfter = 0;
wrapper.vm.displayUserExpAndLvlNotifications(userExpAfter, userExpBefore, userLevelAfter, userLevelBefore);
expect(expSpy).to.be.calledWith(expEarned);
expSpy.restore();
});
});
});

Some files were not shown because too many files have changed in this diff Show More