Compare commits

...

455 Commits

Author SHA1 Message Date
Sabe Jones 93437a00dd 3.78.0 2017-03-02 21:05:49 +00:00
Sabe Jones 31afa0345e chore(news): Bailey 2017-03-02 2017-03-02 20:59:03 +00:00
Sabe Jones b2c278b00a fix(sprites): correct shop canvas 2017-03-02 20:58:42 +00:00
Sabe Jones b54037f230 chore(event): disable Cupid Potions 2017-03-02 20:49:02 +00:00
Sabe Jones 488dcb916a chore(sprites): compile 2017-03-02 20:44:25 +00:00
Sabe Jones 10792b65be feat(content): add Armoire and BGs 2017-03 2017-03-02 20:44:00 +00:00
Sabe Jones 390970a73a chore(news): Bailey 2017-03-01 (#8524) 2017-03-01 18:54:14 -06:00
Megan Tiu 6f0d0b1fb3 Use backticks in 'quest started' system message (#8503)
Fixes #8445
2017-02-26 16:03:58 +10:00
Sabe Jones dfcd32d54a chore(i18n): update locales 2017-02-23 18:04:06 +00:00
Sabe Jones c24cbdc987 feat(content): strings for Armoire and BGs 2017-03 2017-02-23 17:55:50 +00:00
Sabe Jones 68d8c0de51 3.77.0 2017-02-23 05:22:59 +00:00
Sabe Jones a7b0fa195f chore(news): Bailey 2017-02-23 02:57:09 +00:00
Sabe Jones ab65aa692f fix(sprites): NYE 2x pixelfixes
and move a promo to subfolder
2017-02-23 02:56:33 +00:00
Sabe Jones 198d4df02a fix(analytics): consistent partyID label 2017-02-22 20:47:52 +00:00
Sabe Jones 6fa2f643fd fix(event): stop giving out party stuff 2017-02-21 22:21:13 +00:00
Sabe Jones 4d39861b51 fix(event): disable buying Santa quests 2017-02-21 22:16:32 +00:00
Sabe Jones d4c99b6db6 fix(avatar): disable wintery customizations 2017-02-21 21:59:43 +00:00
Sabe Jones a79dc90a45 chore(sprites): compile 2017-02-21 21:21:53 +00:00
Sabe Jones daa6bd0315 chore(event): end Valentine's 2017-02-21 21:21:25 +00:00
Sabe Jones 99f2373214 feat(content): Mystery items 2017/02 2017-02-21 21:20:24 +00:00
Phillip Thelen 374d528647 Implement iOS subscriptions (#8493)
* implement iOS subscriptions

* add additional tests to request body

* Improve subscription cancelling

* change string to constant
2017-02-21 19:22:13 +01:00
Alys 8550ca4d29 change heading on forgot-password form to "Email a Password Reset Link"
Previously it was "Email New Password"
2017-02-19 09:45:58 +10:00
Keith Holliday 79f6b59f6e Options view cleanup (#8484)
* Split the settings into their own files

* Refactor profile jade items to have separate files
2017-02-17 03:55:59 -07:00
Keith Holliday e37ad420ce Updated tests to have no redirect to homes (#8494) 2017-02-16 16:33:49 -07:00
Sabe Jones 2f9ff92cbd 3.76.0 2017-02-16 23:12:55 +00:00
Sabe Jones 56479e7fbd chore(i18n): update locales 2017-02-16 21:07:51 +00:00
Sabe Jones 66e4849553 feat(content): subscriber item strings 2017-02-16 20:56:16 +00:00
Sabe Jones 9e29b44ad9 chore(sprites): compile 2017-02-16 20:51:35 +00:00
Sabe Jones 605488dd47 feat(content): Guinea Pig quest 2017-02-16 20:51:12 +00:00
MathWhiz 6c16b4b77e ApiDoc - Development (#8480)
* Add api info

* Remove extra spaces
2017-02-16 08:53:21 -07:00
Matteo Pagliazzi 80205dcc67 recompile shrinkwrap 2017-02-15 17:25:49 +01:00
Matteo Pagliazzi 65d5bf69f6 Upgrade webpack to v2 and other deps (#8491)
* update to webpack2 and other deps

* fix linting
2017-02-15 17:24:37 +01:00
Matteo Pagliazzi 20792f5455 New Client: stable (#8426)
* try to freeze content object

* deep freeze the content object, start to implement stable

* freeze at the /common level

* go back to freezing content only on the client

* use deep-frezze-strict to support phantomjs

* use own version of deepFreeze

* update comment about deepFreeze
2017-02-15 12:49:57 +01:00
Sabe Jones 6fd509df13 fix(news): missing blog info 2017-02-14 21:00:38 +00:00
Sabe Jones d7df18a97a 3.75.3 2017-02-14 18:29:11 +00:00
Sabe Jones ff5c44c5a8 feat(event): Valentine's 2017-02-14 17:32:23 +00:00
Matteo Pagliazzi c929fa1351 chore(i18n): update locales 2017-02-14 18:19:14 +01:00
Matteo Pagliazzi d30e7b9251 Don't send plaintext reset passwords via email (#8457)
* start work to avoid sending reset password in plaintext via email

* start checking parameters

* fix new password reset email

* render error if password reset code is missing or invalid

* implement POST route, conversion to bcrypt and messages

* add auth.local.passwordResetCode field

* add failing tests, move reset code validation func to lib, fixes, remove old tests

* fix unit tests

* fix page rendering and add integration tests

* fix password reset page

* add integration test

* fix string

* fix tests url
2017-02-14 18:08:31 +01:00
Matteo Pagliazzi c6c6632405 fix(tests): correctly append query string 2017-02-13 12:54:45 +01:00
Matteo Pagliazzi e0d9ecca52 fix(tests): do not redirect to home page when cancelling a subscription 2017-02-13 12:21:34 +01:00
Marcel O'Neil 78f187f87a Task edit screen changes [fixes #8474]
move Dailies repeats out of Advanced Options; enlarge Notes; reduce spacings
2017-02-13 20:02:27 +10:00
Alys af047d3c82 revert commits accidentally added from "Added support for grouping tasks by challenge"
So sorry, but I accidentally pushed some commits I'd been testing. :( This reverts them.

Reverts c496f94c79 ad6073220d d669db0f9a c244fe488d
2017-02-12 12:17:23 +10:00
Alys 20e8f47def adjust links to wiki's page for Extensions, Add-Ons, and Customizations
- remove redundant link from footer's Company column (still exists in Community)
- change link to point directly to wiki page instead of to a wiki redirect for:
   - static/features page ("3rd Party Tools" section)
   - static/extensions redirection
2017-02-11 23:14:47 +10:00
Keith Holliday c496f94c79 Updated setting string 2017-02-11 17:39:53 +10:00
Keith Holliday ad6073220d Fixed broken member test 2017-02-11 17:39:53 +10:00
Keith Holliday d669db0f9a Fixed tests and updated default challenge model name 2017-02-11 17:39:53 +10:00
Keith Holliday c244fe488d Added support for grouping tasks by chllenge 2017-02-11 17:39:53 +10:00
Monica Williams aef71db0f5 removes changes to all local files except for en file (#8463) 2017-02-11 17:06:57 +10:00
Sabe Jones ac566882fe 3.75.2 2017-02-08 22:02:59 +00:00
Sabe Jones 1ad662a090 chore(news): blog Bailey 2017-02-08 20:38:40 +00:00
Rachel Williams 1db52de45d Reordered Magic Hatching Potions, fixes issue 8465 (#8467)
* Moved Royal Purple to top of list, ordered other mounts from Spring to Winter

* Issue 8465, moved Royal Purple to top and arranged other pets/mounts from Spring-Winter

* Fixed whitespacing mistake from previous commit

* Magic Hatching Potions reordered by season
2017-02-08 13:14:30 -06:00
Travis f8cfdfa37d Updating attribute calculation to look at all equipment #8188 (#8202)
* Taking all equipped items into account when calculating attributes instead of just head, weapon, sheild, and armor. closes #8185

* Refactored the stat calculations a bit so that all stat calculations are handled in the same location in the same way to try and reduce duplicate logic.
This commit also adds a number of tests to test this new behavior.

* spelling fixes
2017-02-07 12:04:12 -07:00
Sabe Jones ffd36465c9 chore(packages): update passport 2017-02-07 00:25:41 +00:00
Myles Louis Dakan dbb1e3aa18 Mods blocking (#8364)
* updated logic for blocking/unblocking

* updated 1 test and added 2
2017-02-05 22:32:10 -07:00
Matteo Pagliazzi 07ce68c596 remove newrelic 2017-02-05 18:43:00 +01:00
Keith Holliday 4c5d72c96f Fixed group plan checkout with stripe (#8475) 2017-02-04 09:01:53 -07:00
Matteo Pagliazzi 7121653515 3.75.1 2017-02-03 11:38:55 +01:00
Matteo Pagliazzi ce2b66a2be update deps 2017-02-03 11:15:30 +01:00
Matteo Pagliazzi 43e381cfc1 Revert "Fix Gem purchase validation" (#8471) 2017-02-03 11:08:06 +01:00
Phillip Thelen ca5161b48a fix gem purchase validation (#8470) 2017-02-03 10:58:48 +01:00
Sabe Jones 44dec6dc4b fix(news): Android URL 2017-02-02 22:13:31 +00:00
Sabe Jones fb8f0bed43 3.75.0 2017-02-02 22:08:17 +00:00
Sabe Jones c391de9e86 chore(news): Bailey
and tweak today's migrations for new format
2017-02-02 21:04:36 +00:00
Sabe Jones 7a534ab81d fix(payments): customerId query 2017-02-02 19:51:52 +00:00
Sabe Jones 32c7f40c4b chore(sprites): compile 2017-02-02 17:31:43 +00:00
Sabe Jones 569fbff244 feat(content): backgrounds and Armoire Feb 2017 2017-02-02 17:30:46 +00:00
Sabe Jones 3fe847f329 feat(content): Cupid Hatching Potions 2017-02-02 17:29:33 +00:00
Sabe Jones d52d759733 3.74.1 2017-02-02 15:45:23 +00:00
Sabe Jones 5e5a755022 chore(sprites): compile 2017-02-02 15:18:12 +00:00
Sabe Jones 8d148b4d69 End Winter Wonderland (#8466)
* chore(event): end Winter Wonderland

* fix(test): update constant
2017-02-02 09:08:51 -06:00
Sabe Jones 248b64a43f 3.74.0 2017-02-02 00:59:44 +00:00
Sabe Jones 57ed0f0a10 chore(i18n): update locales 2017-02-02 00:55:58 +00:00
Sabe Jones 3455adaef5 chore(news): Bailey 2017-02-02 00:48:24 +00:00
Phillip Thelen 4d0295a60d Support subscription payment through Google Play Store (#8437)
* Support subscription payment through Google Play Store

* minor fixes to iap subscriptions

* Support subscription payment through Google Play Store

* minor fixes to iap subscriptions

* revert change to test

* add unit tests for google payments

* add integration tests for google payments

* change config formatting for play api

* fix typo in file name

* fix typo in example config

* Improve google payment tests

* fix linter errors
2017-02-01 18:39:37 -06:00
Sabe Jones a002bc5e20 fix(migration): curly typo 2017-02-01 21:00:48 +00:00
Sabe Jones b9d1086e24 chore(migration): update Take This format 2017-02-01 20:52:26 +00:00
madpink 412a0ecc8c Updating Tasks and Tags API Doc (#8447)
* Updating Tasks and Tags API Doc

* Update Tasks and Tag API Doc

added back  * @apiUse ChallengeNotFound

* Update Tasks and Tag API Doc #8447

Corrected NotAuthorized errors to 401
2017-02-01 19:37:27 +01:00
Sabe Jones d44c9ea853 3.73.1 2017-01-31 21:11:21 +00:00
Sabe Jones c44c581265 fix(shops): correct event boolean 2017-01-31 21:10:51 +00:00
Sabe Jones f666f3cd04 3.73.0 2017-01-31 19:22:22 +00:00
Sabe Jones 236bd6cec4 fix(test): linting and constant 2017-01-31 19:21:13 +00:00
Keith Holliday e124c36274 Added double columns to edit modal (#8456)
* Added double columns to edit modal

* Fixed task notes z index and border styles

* Moved tags to right column and updated min width
2017-01-31 12:05:49 -07:00
Sabe Jones d5511a0047 chore(i18n): update locales 2017-01-31 18:38:23 +00:00
Sabe Jones 87f003f392 chore(sprites): compile 2017-01-31 18:32:03 +00:00
Sabe Jones 930a869365 feat(event): Habit Birthday 2017 2017-01-31 18:30:39 +00:00
Alys 809da8add0 update landing page's number of user accounts to 2 million
YES! TWO MILLION!
2017-01-31 20:54:33 +10:00
Sabe Jones 1dad176320 3.72.1 2017-01-26 18:52:39 +00:00
Sabe Jones 1596c6218f chore(news): Bailey 2017-01-26 2017-01-26 18:10:13 +00:00
Sabe Jones 0d3aba950a fix(config): remove deprecated env var 2017-01-25 23:27:34 +00:00
Sabe Jones f1110e0f89 chore(i18n): update locales 2017-01-25 22:31:54 +00:00
Sabe Jones 7273f8f6d9 feat(content): strings for upcoming items 2017-01-25 22:25:32 +00:00
Keith Holliday a0ae200a54 Removed extra async (#8455) 2017-01-25 09:47:21 -07:00
Sabe Jones ca448f081d 3.72.0 2017-01-25 00:35:36 +00:00
Keith Holliday cd27afa9f0 Used profile name in inviter message (#8453) 2017-01-24 17:44:45 -06:00
Sabe Jones 8bc8183895 chore(i18n): update locales 2017-01-24 21:44:48 +00:00
Sabe Jones c26c52f1fe chore(sprites): compile 2017-01-24 21:37:57 +00:00
Sabe Jones 8d5becc9ce feat(content): subscriber items 2017-01 2017-01-24 21:30:16 +00:00
Matteo Pagliazzi cf7f6e2a67 chore(i18n): update locales 2017-01-24 20:23:20 +01:00
Matteo Pagliazzi acad3b8873 Migrate to bcrypt (#8446)
* start migrating to bcrypt

* added method to convert the password to bcrypt when logging in, added method to compare password without knowing the hashing algorhytm, remove default

* travis: try to upgrade to container based infrastructure

* travis: add deps to build bcrypt.js

* travis: add deps to build bcrypt.js

* travis: add deps to build bcrypt.js

* travis: add deps to build bcrypt.js

* use bcryptjs until bcrypt can be installed on travis, see https://github.com/kelektiv/node.bcrypt.js/issues/476

* correct sha1 unit tests

* try different mongodb repo

* try without mognodb services

* try again with bcrypt

* disable request logging in travis

* migrate missing routes

* simplify code

* remove bcryptjs

* fix typo

* fix typo

* fix typo in comment

* add unit tests for new passwords utility emthods

* travis: back to old infrastructure, containers often have timeouts

* add integration test for passwordHashMethod

* update shrinkwrap

* clarify code and add comments

* add integration tests

* fix linting

* fix integration tests
2017-01-24 12:28:42 +01:00
Jaka Kranjc 04f4eb8490 added test case for #8423 (#8434) 2017-01-24 11:15:08 +01:00
Jaka Kranjc 8c8af83dfc spells: searing brightness should not affect challenge tasks (#8427)
* spells: searing brightness should not affect challenge tasks

* fixed other incorrect group vs challenge task exclusions

* fixed /tasks/clearCompletedTodos test

didn't account for the new group task

* fixed comment omission in tasks/clearCompletedTodos
2017-01-24 11:14:25 +01:00
Valeriy 86d65956d9 fixed recoverCron tests failure when using mocha v3+ (#8407)
* fixed recoverCron tests

* type'o fix

* grammar nazi fix

* lint fix

* reverted to async/await (but done call removed)

* fixed Tasks tests failure on Mocha 3+
2017-01-24 10:14:27 +01:00
MathWhiz 42c5e6c22b Facebook pixel Revert (3rd time's a charm?) (#8450)
* attempt revert

* Add back unrelated stuff
2017-01-23 14:42:57 -06:00
Cai Lu 79b51a40ce Validate coupon code for subscriptions fixes #8398 (#8440)
* (fix) If coupon is not valid, display an error

* (test) Add valid and invalid coupon tests for applyCoupon

* Add test descriptions
2017-01-23 12:01:30 -07:00
Matteo Pagliazzi 79c3efaf9c 3.71.0 2017-01-23 19:05:39 +01:00
Matteo Pagliazzi 74c6a891fc Revert "Revert Facebook Pixel" (#8449) 2017-01-23 16:38:56 +01:00
MathWhiz 9a5d17f538 attempt revert (#8406) 2017-01-23 16:03:23 +01:00
Matteo Pagliazzi 070c4a8fbd add auth.local.passwordHashMethod field 2017-01-23 10:38:41 +01:00
Keith Holliday 2bbc4f4f4d Paypal refactor lib (#8420)
* Abstracted paypal logic from controller. Added intial tests

* Added checkout tests

* Added checkout success test

* Added subscribe test

* Added subscribeSuccess tests

* Added subscribeCancel tests

* Added ipn tests

* Fixed broken test

* Added integration tests and fixed linting issues

* Added errors for paypal checkout success integration test

* Removed extra test

* Removed pending status from subscribe cancel test

* Added more error checking and tests

* Fixed lint issues
2017-01-22 09:59:47 -07:00
Alys 39c00ea433 change HTML <title> on static/old-news from "Old News" to "News"
That page now contains the most recent news as well as the old news.
2017-01-22 13:32:54 +10:00
Alys dd6c1c764a change footer's "Add-ons & Extensions" link
The new link is a better wiki page and is already in use in the Settings > API screen.
2017-01-22 13:15:12 +10:00
Sabe Jones 9b456d1760 3.70.0 2017-01-21 03:13:36 +00:00
Sabe Jones acf1031317 chore(i18n): update locales 2017-01-21 02:50:49 +00:00
Sabe Jones 5d45204d8b chore(sprites): compile 2017-01-21 02:41:31 +00:00
Sabe Jones 37a71924fe Incentives, Batch 2 (#8435)
* feat(content): Incentives batch 2 gear

* feat(incentives): incentives 55-100

* chore(migration): hand out missed incentive

* refactor(constant): export MAX_INCENTIVES

* fix(incentives): correct const import
and say "Royal Purple Potion" not "Royal Purple"
2017-01-20 20:36:38 -06:00
Alys 9cf2408988 change name of Newbies Guild to Habitica Help: Ask a Question guild (#8424)
* change name of Newbies Guild to Habitica Help: Ask a Question guild

* remove ": Ask a Question" from "Habitica Help: Ask a Question" in locations where we are linking to the guild or its wiki page
2017-01-21 09:20:39 +10:00
Keith Holliday 638525f8d8 Stripe refactor to lib (#8417)
* Moved stripe tests to folder

* Abstracted stripe payments logic to lib

* Added initial unit test for stripe payment

* Added subscription tests

* Added tests for regulare purchases

* Added tests for edit subscription

* Added cancel tests

* Added integration tests

* Fixed lint issues

* Fixed lint issue
2017-01-20 16:08:52 -07:00
Keith Holliday 2c37ba3cee Added migration runner that allows for migrations to use server code (#8436)
* Added migration runner that allows for migrations to use server code

* Replaced example script in migration runner
2017-01-20 16:08:01 -06:00
Sabe Jones ad5b2fe540 3.69.1 2017-01-19 23:02:41 +00:00
Sabe Jones bfb6daad51 fix(docs): separate def blocks 2017-01-19 23:02:22 +00:00
Sabe Jones 281b8e2b7c 3.69.0 2017-01-19 22:09:56 +00:00
Sabe Jones 3f88ea2378 fix(typo): extra brackets 2017-01-19 22:04:36 +00:00
Sabe Jones 9c6275d4ab chore(i18n): update locales 2017-01-19 20:31:23 +00:00
Sabe Jones fd3c8ddc8b chore(sprites): compile 2017-01-19 20:22:32 +00:00
Sabe Jones 72f47ad4e6 chore(news): misc Bailey 2017-01-19 20:19:15 +00:00
ean 74c9a1b02d Add missing text to buttons on the party / guild invite modal - Fixes #8391 (#8402)
* Fix missing text in party / guild invite modal

* add tests and abstract logic

* use translation lib for test
2017-01-19 12:17:38 -07:00
Keith Holliday ffa561473c [WIP] Amazon refactor to lib (#8403)
* Moved amazon tests to folder

* Abstracted amazon payment code and added initial test

* Abstracted cancel and subscribe logic to amazon payment lib

* Added arg checks to checkout

* Added constants. Added more subscription test

* Added with arg checks to cancel

* Fixed linting issues

* Added integration tests for amazon subscribe cancel

* Added integration test for amazon checkout

* Added integration test for amazon subscribe

* Added coupon unit test

* Fixed lint

* Fixed minor test issue and changed header expectations

* Fixed line endings
2017-01-19 08:30:30 -07:00
MathWhiz 080ffae4e1 apiDoc /chat Documentation (#8277)
* apiDoc /chat Documentation

* Add {NotFound} to FlagOwnMessage error

* Quick changes

* Update chat.js
2017-01-18 22:28:00 -07:00
Gerardo Saca e395182c95 Fix User > Profile showing {getProgressDisplay()} - fixes #8412 (#8421)
* Fix User > Profile showing {getProgressDisplay()}

* Remove bad nextRewardAt check
2017-01-18 22:25:37 -07:00
Matteo Pagliazzi 68f4275c44 fix tests failures (#8422)
* fix tests failures

* make sure _meta is not public

* fix typo

* fix typo
2017-01-18 20:17:56 +01:00
Alys 4bf4c3a6c2 remove Windows line breaks 2017-01-19 05:00:54 +10:00
Sabe Jones f00bb29192 fix(quest): correct pt 2 label 2017-01-18 16:25:52 +00:00
Alyssa Batula 016447ec77 Critical Hits now affect boss damage fixes #5429 (#8092)
* Moved critical hit calculation from _addPoints() to _calculateDelta(). Added user as an input argument to _calculateDelta() so for critical hit calculation

* Changed test to expect task value of 1.5 after critical hit

* Revert "Moved critical hit calculation from _addPoints() to _calculateDelta(). Added user as an input argument to _calculateDelta() so for critical hit calculation"

This reverts commit 51b8ab6498.

* Moved critical hit calculation to _changeTaskValue(). Use value stored in user._tmp.crit in _addPoints()

* Test is no longer affected by critical hits

* Removed unneeded comment

* Added WIP test of critical hits

* Want the crit function to return 2 to test critical hits

* Changed crit function to export as a function within an object so that it can be stubbed for testing. References to the crit() function were updated to call crit.crit() instead

* Added test for increased experience on critical hits
2017-01-18 08:11:39 -07:00
Dumindu Buddhika fa024e071b added message saying quest is started (#8107)
* added chat message saying quest has started

* added info to message

* added information as meta data

* fixed failing test case

* added new tests
2017-01-18 08:08:47 -07:00
Keith Holliday 28fec237fe Group plans misc fixes (#8388)
* Added notification for approval request in the group leaders language

* Added test for group task meta actions. Added sync when user claims

* Added tests for group task actions. Ensured assigned members are synce when added or removed

* Fixed approval required toggle

* Added support for users with comma in their name

* Fixed sync issue when user is approved and reloads the website

* Added advance options for group rewards

* Added back ticks to group claim message

* Fixed disappearing tasks that need approval

* Up chat limit to 400 for subbed groups

* Fixed line endings

* Updated activie subscription check

* Added group isSubscribed function

* Changed to isAfter
2017-01-18 07:54:49 -07:00
Sabe Jones e4bb82768c fix(sprites): add large-1 to repo 2017-01-18 01:52:14 +00:00
Sabe Jones 65eca22407 3.68.0 2017-01-18 01:26:58 +00:00
Sabe Jones cea1597ee1 chore(i18n): update locales 2017-01-18 00:27:01 +00:00
Sabe Jones 3906952154 chore(sprites): compile 2017-01-18 00:16:01 +00:00
Sabe Jones 6169b9d0ae feat(content): new Gold quests (#8418)
and Wintery Skins, and fixes #8412
2017-01-17 16:03:10 -08:00
Declan Ramsay 69cac7e504 Add markdown formatting to task-edit title text (#8416) 2017-01-17 14:49:16 -07:00
mleah febf3f0024 Adding "How to gift subscription" copy to Settings > Subscription Page: Fixes Issue 8341 (#8386)
* Issue 8341 updating subscriber json and subscription settings page with gifting a subscription directions

* Issue-8341 updating the layout of the settings - subscription page

* Issue-8341 removing extra comma from gift subscription copy

* Issue-8341 removing extra spans in subscription jade file
2017-01-17 14:48:21 -07:00
Matteo Pagliazzi 563f40e4b7 client: reorganize files, router and add inventory skeleton 2017-01-17 19:45:27 +01:00
Matteo Pagliazzi e2b06161e1 client: add task component 2017-01-17 18:07:02 +01:00
Alys e7de8b8e2f clarify two Notifications descriptions (onboarding, inactive account)
New words were supplied by Lemoness.
2017-01-17 07:32:35 +10:00
Matteo Pagliazzi a0624d9507 fix build by not requiring optional package in prod 2017-01-16 18:31:29 +01:00
Matteo Pagliazzi cddd0df4f2 chore(i18n) add overview and login incentives 2017-01-16 17:59:04 +01:00
Matteo Pagliazzi 220bfb3517 chore(i18n): update locales 2017-01-16 17:58:30 +01:00
Matteo Pagliazzi 2106a5ebd3 correct deps loading and shrinkwrap 2017-01-15 23:45:22 +01:00
Matteo Pagliazzi bbffa9830b client: replace deprecated vue-resource with axios, lint more file 2017-01-15 20:49:15 +01:00
Matteo Pagliazzi caa546eb62 client: add missing test files and use v-once 2017-01-15 20:08:36 +01:00
Alys 4e83059652 remove outdated information about changing your email address
This is being done to remove a redundant string that
contains an email address, in preparation for actioning
https://github.com/HabitRPG/habitica/issues/8385 ("Email addresses
should not probably be translatable")
2017-01-15 22:50:32 +10:00
Matteo Pagliazzi 903cdb36ef client: remove old code 2017-01-14 21:46:28 +01:00
Matteo Pagliazzi d8128cc3db New Client: english translations and misc fixes (#8410)
* misc fixes and add english translations

* add tests
2017-01-14 21:12:11 +01:00
MathWhiz f888e80b01 apiDoc: meta (#8167)
* apiDoc: meta

* Update modelsPaths.js

* Update error

* fix test

* Update GET-model_paths.test.js

* Fixed test
2017-01-13 14:06:46 -07:00
Matteo Pagliazzi fd7aedbff2 chore(locales): add merch file 2017-01-13 19:09:47 +01:00
Matteo Pagliazzi 6c5234313d chore(i18n): update locales 2017-01-13 19:01:26 +01:00
Sabe Jones 08aa5758b4 3.67.1 2017-01-13 00:05:39 +00:00
Sabe Jones 1415e344c0 chore(i18n): update locales 2017-01-12 23:24:04 +00:00
Sabe Jones 2ce7915f06 fix(incentives): return 0 for upcoming at end of list
Also adds some new strings for upcoming content, adds a news announcement, and compiles a recent sprites fix.
2017-01-12 23:15:37 +00:00
MathWhiz 838c8b4e08 Update Royal Purple Flying PIg (#8401) 2017-01-12 15:06:25 -08:00
Keith Holliday 1590d955cd Group plans reorder tasks (#8358)
* Added move route for group tasks

* Added group task reorder to front end

* Added syncing with group task order

* Fixed linting issues

* Added missing exec and abstracted move code

* Added unit test for moveTask
2017-01-11 19:16:20 +01:00
MathWhiz 2690caed35 News translation link (#8393)
* Add translation link to news

* Add newsArchive string

* Translate news archive link

* Fix link?

* Add link to wiki
2017-01-11 09:16:10 -07:00
Sabe Jones dc2d4fa10b fix(coveralls): update badge for /habitica 2017-01-10 22:25:40 -06:00
Sabe Jones 1540ec89ee 3.67.0 2017-01-10 20:49:54 +00:00
Sabe Jones f304d4fe52 chore(migration): update Bailey to use monk 2017-01-10 20:47:58 +00:00
Sabe Jones 023e433a5c feat(content): Triceratops pet quest 2017-01-10 20:32:21 +00:00
Declan Ramsay ef4aeb29ab Fixes task left in odd setting when cancelling editing with ESC / Escape Key; add click outside to close modal #8308 #8321 (#8382)
* Remove backdrop property from open modal call to allow click-outside to close, call cancelTaskEdit function on dismissal of edit modal via ESC or click-outside

* Remove commented out code, change catch function to normal (non-arrow)

* Modify task services test mock openModal function, to handle new use of subsequent promise resolution functionality

* Tidy old edit controls away
2017-01-10 12:04:34 -07:00
Jan Jorgensen 2b80931202 Fix home column style on medium screen widths fixes #8372 (#8387)
* handle medium screen width on home columns

* rename new-row helper used on home screen for todo column flow
2017-01-10 12:03:21 -07:00
myoshuGO 2950713712 Fixes #8227 (#8380)
* Fixes #8227

* Fixes #8227

* Fixes #8227
2017-01-10 12:00:53 -07:00
Jaka Kranjc 118f3bd1bb gulp/gulp-transifex-test.js: fixed variable name typo (#8389) 2017-01-10 10:07:35 +00:00
Alys 69f1343ea8 correct apidocs: /api/v3/user/... not /user/... 2017-01-09 19:57:26 +00:00
Keith Holliday 918ee02d64 Added hp back to party member query (#8378) 2017-01-07 20:59:17 -06:00
Sabe Jones 0cac34dd26 3.66.3 2017-01-07 22:42:50 +00:00
Sabe Jones 1c859fc91f chore(promo): end gift subs promo 2017-01-07 15:42:03 +00:00
Keith Holliday 857aa5827b Ensured group tasks are removed from places that challegnes tasks are (#8359)
* Ensured group tasks are removed from places that challegnes tasks are

* Added tests for user reset and class cast
2017-01-07 12:01:12 +01:00
Matteo Pagliazzi 28e8ec2d2c add BOSS_DAMAGE to valid notifications 2017-01-07 11:47:19 +01:00
Matteo Pagliazzi 856f13d213 chore(i18n): update locales 2017-01-07 11:43:53 +01:00
Sabe Jones 121fd38bd1 3.66.2 2017-01-06 21:28:35 +00:00
Keith Holliday 36d72f5f7a Claim task messages are now system messages (#8375) 2017-01-06 13:00:23 -08:00
Keith Holliday f1b8bd80e7 Added fix to correctly check if object is sub group for all users (#8377) 2017-01-06 14:09:57 -06:00
Brian David 84d2ce6a3f Fixes issue #8315 (https://github.com/HabitRPG/habitica/issues/8315) (#8367) 2017-01-06 11:45:13 -06:00
Keith Holliday 76010e6c8f Added migration for profile restoration (#8355)
* Added migration for restoring profile data

* Updated migrations to use monk

* Fixed line endings

* Moved monk to dev dependencies
2017-01-06 11:07:21 -06:00
Keith Holliday c707b6c99b Add check to ensure obj is not user (#8373) 2017-01-06 10:23:39 -06:00
Sabe Jones e4bd466cc7 3.66.1 2017-01-06 02:37:50 +00:00
Sabe Jones 001b8eb894 chore(news): Bailey 2017-01-05 2017-01-06 02:09:59 +00:00
Keith Holliday 9abcfe8614 Added fixes for party group plans (#8366) 2017-01-05 16:52:05 -06:00
Matteo Pagliazzi bc6102551d chore(i18n): update locales 2017-01-05 16:42:43 +01:00
Sabe Jones 959a3ff85b fix(menus): keep icon after group ack
Also corrects a TypeError in the menu closing function.
2017-01-04 22:19:57 +00:00
Matteo Pagliazzi 518b874f64 Always use .exec() for .find*() and .update() (#8361)
* add exec where missing in /models

* ix taskManager query

* fix top-level controllers

* fix api-v3 controllers
2017-01-04 16:49:43 +01:00
Travis 6cc359a935 Adding new user.addNotification method with Mongodb Update (#8272)
* Adding new method to user schema that pushes a new notification to the database with an update operation instead of a save. fixes #8264

* fixing test text

* changing the addUserNotificationUpdate method to a static method as requested.

* Renaming to push notification

* fixing comment documentation based on pr comments.

* Changed the update statement to do a multi update and added a validation step. Added tests for both these cases.

* Updating pushNotification method to allow a query to be passed instead of an array of userIds to make it more flexible.

* Removing createdAt field from tests as it's no longer used.

* Removing only from test suite
2017-01-04 15:27:54 +01:00
Sabe Jones 514d35c0be 3.66.0 2017-01-03 22:03:09 +00:00
Sabe Jones 13da92ea68 feat(content): Armoire and BGs 2017-01 2017-01-03 21:35:23 +00:00
Travis 03c4d82b7d fix: prevents blank messages from being posted to chat (#8257)
* fix: throws an error when the server receives a post chat request with a message containing only whitespace.

* Adding a test confirming behavior around messages that only contain newlines.

* Removing accidental only that was left on a test
2017-01-03 07:15:31 -07:00
Sabe Jones d905ab7f86 3.65.2 2017-01-03 00:50:46 +00:00
Sabe Jones c6560b6b1b chore(event): end New Year's bennies 2017-01-03 00:19:20 +00:00
Keith Holliday c61f660255 Added field existence checks (#8356) 2017-01-02 16:31:04 -07:00
Matteo Pagliazzi 2f1b683ec9 Avoid setting profile name to not found (#8357)
* avoid setting profile name to not found

* only set profile name when empty

* profile.name is required

* set profile name before validation

* fix and add tests
2017-01-03 00:00:01 +01:00
chan_gami 47bb217068 Fixes pressing enter to confirm character choice in IME creates new checklist line (#8326) (#8334) 2017-01-02 10:03:03 -07:00
Declan Ramsay f49fd05da1 Fixes on-hover notes on tasks do not update after you edit the notes, until a sync occurs (#8353)
* Shift taskPopover from one-time-binding to regular binding, allowing task note display to be updated

* Re-add Markdown filter to task popover
2017-01-02 10:00:35 -07:00
Matteo Pagliazzi b0341aa06f chore(i18n): update locales 2017-01-02 15:54:04 +01:00
Kaitlin Hipkin b07ec18e33 Correct party up/on achievement string names (#8313)
* Revert party up/on achievement string names

* fix missed references to party up/on strings
2017-01-01 10:54:49 -07:00
Alys 12930a2bac 3.65.1 2016-12-31 07:47:49 +00:00
Alys 91f5c47d9d Revert "feature: adding hp notification for boss damage" (#8340) 2016-12-31 17:31:25 +10:00
Sabe Jones fe7850d10f 3.65.0 2016-12-30 23:30:42 +00:00
Sabe Jones c5c2da75bf fix(shops): hardcode NYE card 2016-12-30 23:08:01 +00:00
Sabe Jones 969607cd3b feat(event): New Year's 2016 2016-12-30 22:19:29 +00:00
Travis 2a1f52a359 feature: adding hp notification for boss damage (#8249)
* feature: adding hp notification for boss damage. fixes #7749

* Updating boss damage text to 'Damage from Boss' to make it more clear
2016-12-30 13:29:20 -06:00
Keith Holliday 47d9594679 Group plans remove unlinked tasks (#8332)
* Added ability to delete tasks that are broken

* Added ability to delete group tasks after leaving group
2016-12-30 13:17:39 -06:00
Keith Holliday 97e40c81f3 Added error when nonleader attempts to invite to group plan (#8331) 2016-12-30 13:17:22 -06:00
Sabe Jones c8b61a2f7d feat(content): Armoire and BG strings 2017-01 2016-12-29 20:43:15 +00:00
Cai Lu e9543f0d28 Add 'balanceGemAmount' to Amplitude #8057 (#8323)
* Add balanceGemAmount property

* Add check for balanceGemAmount property

* Fix balanceGemAmount to be 4 times balance
2016-12-29 14:31:30 -06:00
Keith Holliday 77b88490e4 (WIP) Thehollidayin/front end updates (#8278)
* Added read me and Inbox Page

* Fixed inbox linting

* Added converstaion route

* Added temp data and style

* Added social page and nav

* Fixed Inbox routes

* Added basic layout for Tavern Page
2016-12-29 13:24:08 -06:00
Keith Holliday 7fc2500bfd Removed group plan option from gift modal (#8327) 2016-12-29 08:57:00 -06:00
Keith Holliday fb229acb58 Added per user cost message if group has subscription (#8328)
* Added per user cost message if group has subscription

* Added user specification to cost
2016-12-29 08:56:40 -06:00
Sabe Jones 6ce83d1fa4 3.64.1 2016-12-28 22:03:35 +00:00
Sabe Jones 2be4815aea chore(news): misc Bailey 2016-12-28 21:59:20 +00:00
Khwunchai Jaengsawang 1dbc42f48a Fixes character appears too high in avatar on profile when no pet or mount is equipped (#7916) (#8318) 2016-12-28 09:36:56 -06:00
Matteo Pagliazzi 89279c8aed chore(i18n): update locales 2016-12-28 09:53:20 +01:00
Matteo Pagliazzi faedb13598 add schema field to keep track of onboarding emails 2016-12-28 09:47:14 +01:00
Rick Kasten c0c74659c5 Docker improvements (#8297)
* Improved docker, bower

* npm install missing mocha

* Fix for 'npm install -g npm@4' not resulting in a functional npm

* Improve speed of 'docker start' by withholding directories not used by image environment

* Reverting changes to bower.json
2016-12-28 09:33:24 +01:00
Travis bf5ad2db1f Fixing Exponential Quest Reward Scrolls (#7800)
* adding quest owner specific rewards. closes #2715

* Updating model to prevent this from being a breaking change.

* Removing duplicate translatable string and readding accidentally deleted portion

* capitalizing according to pr.

* fixing according to comments on pr

* removing final mistakes

* fixing whitespace

* re-adding the onlyOwner field that got deleted when the index.js file was moved and fixed console errors.

* moving cleaning of empty obejct for quest owner updates into quest owner updates method

* Fixing so tests pass by updating variable name and removing unnecessary parameter definition.

* adding a new test and refactoring client side code to use controller method.
2016-12-28 01:38:52 -06:00
Megan Tiu 7d99873960 Add inline save-close buttons to top of task modal (#8319) 2016-12-28 01:24:58 -06:00
Keith Holliday e02ef00397 Add leader check for challenge tasks on delete icon (#8325) 2016-12-28 01:08:37 -06:00
Alys 23c5c4211c decapitalise "A" in a title; Windows character removal; spacing fixes
- decapitaliseed "A" in "Create A Group"
- converted non-ascii apostrophes to single-quote for consistency with other strings
- removed a double space within a string
- removed Windows line endings on every line
- converted all leading four spaces to two spaces (normally wouldn't do that but since every line was already touched by the line ending change, why not)
2016-12-28 05:07:21 +00:00
Matteo Pagliazzi 69cc134fff 3.64.0 2016-12-27 17:18:59 +01:00
Matteo Pagliazzi ffd9400cb7 fix: eslint: re-order packages 2016-12-26 16:04:36 +01:00
Matteo Pagliazzi 5be91ef842 fix test that was not passing because languages are not loaded in tests 2016-12-26 15:31:31 +01:00
Matteo Pagliazzi 3123183e46 chore(i18n): update locales 2016-12-26 11:59:44 +01:00
Matteo Pagliazzi 49cca7a601 WIP - Onboarding emails (#8309)
* add onboarding emails

* fix typo
2016-12-26 11:49:45 +01:00
Keith Holliday 7fbd38d18c Checked to ensure tasks has checklists before attempting to sync last checklist item (#8314) 2016-12-25 15:00:41 -06:00
padm0 1f95376d39 added missing pixels to robe fixes #8289 (#8296) 2016-12-26 03:06:34 +10:00
Keith Holliday 2da0a1e88c Task interaction fixes (#8306)
* Fixed interacting with a broken challenge

* Added fix for users using open tasks in edit mode
2016-12-22 11:40:00 -06:00
Keith Holliday afacd3e1cf Replaced array deconstruction with object (#8300) 2016-12-21 18:09:45 -06:00
Sabe Jones a69b9e6705 3.63.0 2016-12-21 23:41:41 +00:00
Sabe Jones e4e5d10316 Mixed type field for A/B testing (#8302)
* feat(AB-testing): mixed type field

* fix(AB-testing): lint errors

* fix(AB-testing): allow client access to _ABtests

* Revert "fix(AB-testing): allow client access to _ABtests"

This reverts commit 25832365ba.

* fix(AB-testing): preview check on server

* refactor(AB-testing): add comments
2016-12-21 15:19:00 -08:00
Sabe Jones 27c38bdf45 feat(content): subscriber items 2016-12 2016-12-21 20:34:45 +00:00
Keith Holliday ea24eeb019 Thehollidayinn/group plans part 2 (#8262)
* Added all ui components back

* Added group ui items back and initial group approval directive

* Added approval list view with approving functionality

* Added notification display for group approvals

* Fixed linting issues

* Removed expectation from beforeEach

* Moved string to locale

* Added per use group plan for stripe

* Added tests for stripe group plan upgrade

* Removed paypal option

* Abstract sub blocks. Hit group sub block from user settings page. Added group subscriptin beneifts display

* Fixed lint issue

* Added pricing and adjusted styles

* Moved text to translations

* Added group email types

* Fixed typo

* Fixed group plan abstraction and other style issues

* Fixed email unit test

* Added type to group plan to filter our group plans

* Removed dev protection from routes

* Removed hard coding and fixed upgrade plan

* Added error when group has subscription and tries to remove

* Fixed payment unit tests

* Added custom string and moved subscription check up in the logic

* Added ability for old leader to delete subscription the created

* Allowed old guild leader to edit their group subscription

* Fixed linting and tests

* Added group sub page to user sub settings

* Added approval and group tasks requests back. Hid user group sub on profile

* Added group tasks sync after adding to allow for editing

* Fixed promise chain when resolving group

* Added approvals to group promise chain

* Ensured compelted group todos are not delted at cron

* Updated copy and other minor styles

* Added group field to tags and recolored group tag.

* Added chat message when task is claimed

* Preventing task scoring when approval is needed

* Added approval requested indicator

* Updated column with for tasks on group page

* Added checklist sync on assign

* Added sync for checklist items

* Added checkilist sync when task is updated

* Added checklist sync remove

* Sanatized group tasks when updated

* Fixed lint issues

* Added instant scoring of approved task

* Added task modal

* Fixed editing of challenge and group tasks

* Added cancel button

* Added add new checklist option to update sync

* Added remove for checklist

* Added checklist update

* Added difference check and sync for checklist if there is a diff

* Fixed task syncing

* Fixed linting issues

* Fixed styles and karma tests

* Fixed minor style issues

* Fixed obj transfer on scope

* Fixed broken tests

* Added new benefits page

* Updated group page styles

* Updated benefits page style

* Added translations

* Prevented sync with empty trask list

* Added task title to edit modal

* Added new group plans page and upgrade redirect

* Added group plans redirect to upgrade

* Fixed party home page being hidden and home button click

* Fixed dynamic changing of task status and grey popup

* Fixed tag editing

* Hid benifites information if group has subscription

* Added quotes to task name

* Fixed issue with assigning multiple users

* Added new group plans ctrl

* Hid menu from public guilds

* Fixed task sync issue

* Updated placeholder for assign field

* Added correct cost to subscribe details

* Hid create, edit, delete task options from non group leaders

* Prevented some front end modifications to group tasks

* Hid tags option from group original task

* Added refresh for approvals and group tasks

* Prepend new group tasks

* Fix last checklist item sync

* Fixed casing issue with tags

* Added claimed by message on hover

* Prevent user from deleting assigned task

* Added single route for group plan sign up and payments

* Abstracted stripe payments and added initial tests

* Abstracted amazon and added initial tests

* Fixed create group message

* Update group id check and return group

* Updated to use the new returned group

* Fixed linting and promise issues

* Fixed broken leave test after merge issue

* Fixed undefined approval error and editing/deleting challenge tasks

* Add pricing to group plans, removed confirmation, and fixed redirect after payment

* Updated group plan cost text
2016-12-21 13:45:45 -06:00
Travis 55a8eef3e1 Fixing Duplicate tasks showing up after joining a challenge (#7787)
* fix: prevents double joining challenge by quickly hitting join button on challenge twice. fixes #7730

* Fixing client side parameter updates.
2016-12-20 19:52:18 -06:00
Matteo Pagliazzi 92cbb4a07d Upgrade ESLint to v3 (#8299)
* upgraded habitrpg-eslint-config to v2 and eslint to v3

* adapt to eslint3 rules

* update shrinkwrap

* update shrinkwrap again
2016-12-20 22:31:36 +01:00
Vince Campanale 3f96d05365 Fix gemgift spacing (#8294)
* included privateMessageGiftIntro in privateMessageGiftGemsMessage to take care of spacing error and make it easier for translators

* fixed spacing error in gems gift message and adjusted relevant tests

* removed privateMessageGiftIntro and privateMessageGiftSubscriptionMessage since they are no longer in use
2016-12-20 15:32:59 +01:00
Matteo Pagliazzi 0b72f6a613 chore(i18n): update locales 2016-12-19 21:44:04 +01:00
Matteo Pagliazzi 5e1e6be518 models: add required: true to id fields where missing 2016-12-18 17:16:49 +01:00
Matteo Pagliazzi 472ec99291 chore(i18n): update locales 2016-12-17 15:17:53 +01:00
Sabe Jones 0284e9a4e3 fix(event): sell Santa scrolls 2016-12-17 02:54:29 +00:00
Sabe Jones 1a0721c078 3.62.0 2016-12-17 02:17:50 +00:00
Sabe Jones 6b6b548ac5 chore(sprites): compile 2016-12-17 02:16:01 +00:00
Sabe Jones 30f3d786bb feat(event): Winter Wonderland 2016-2017 (#8290) 2016-12-16 17:49:22 -08:00
padm0 07bbba6789 newly designed peppermint panda mount. Original images were incorrect drawings. fixes #7982 (#8286) 2016-12-16 16:53:09 -08:00
padm0 6afb2bd0d4 adjust positioning on mounts fixes #7982 (#8285)
* adjust positioning on mounts fixes #7982

* fixing background on peppermint flying pig
2016-12-16 16:46:37 -08:00
Jaka Kranjc f1a3bd5001 transferGems: use receiver language translation for PM strings #7722 (#8173)
* transferGems: use receiver language translation for PM strings #7722

* chore(test): DRY up transfer gems test

* chore(test): Allow check for language in translation assertion

* chore(test): Add test that member locales are used in transfer gems msg

* sendMessage: optionally take also the message in sender's language

when present, it is stored in the sender's inbox instead of the version
in the target language.

* transferGems: prepare pm in both languages #7722

* sendMessage: take an object for the second parameter instead

* payments: made two more gift strings translatable

* buyGems: send both translations for gifted gems

* buyGems: send push notifications in target user's locale

* createSubscription: send both translations for gifted subs

* createSubscription: send push notifications in target user's locale

* transferGems: send push notifications in target user's locale

* tests: adjust payment tests for translation changes

* added function doc for sendMessage

* tests: added bilingual test for buyGems
2016-12-15 18:36:54 -06:00
Sabe Jones 3f6a13d209 fix(achievements): don't show unobtainable boss quests 2016-12-16 00:04:46 +00:00
Sabe Jones 3658e41fec fix(jade): eliminate space warning, for real 2016-12-15 21:42:00 +00:00
Sabe Jones c69d5c7ae6 fix(achievements): don't return undefined 2016-12-15 21:35:21 +00:00
Sabe Jones 747f9e6a99 fix(achievements): show boolean pet cheevos
Also fixes a spacing issue that threw Jade warnings.
2016-12-15 21:03:55 +00:00
Matteo Pagliazzi 7755ab090b chore(i18n): update locales 2016-12-15 21:57:45 +01:00
Travis 9ed17df1e3 Updating group.removeMember api to send an email for rescinded party invite (#8280)
* feature: updating group.removeMember api to send an email to a user when an invite was rescinded or when no message was provided by the performing user.

* Adding validation that the email is being sent to the right user.

* fixing linting error
2016-12-15 20:47:18 +01:00
Sabe Jones faeb040a83 fix(achievements): show Rebirths count 2016-12-15 17:49:15 +00:00
Sabe Jones 0a1ae1375e fix(achievements): camelcase altPath 2016-12-15 17:43:54 +00:00
Sabe Jones 9756030fa2 3.61.0 2016-12-15 17:35:51 +00:00
Sabe Jones c66172b74b chore(sprites): compile
and update Bailey date
2016-12-15 16:50:09 +00:00
Sabe Jones 281f6d1806 Holly Potions (#8281)
* feat(content): Wonderland 2016 gear strings

* feat(content): Holly Potions
and string data for Winter Wonderland and December subscriber gear

* fix(event): correct winter availability

* refactor(canBuy): concise return logic

* chore(news): Bailey
2016-12-15 08:36:28 -08:00
Matteo Pagliazzi 237095d109 Notifications: remove timestamps (#8287)
* user notifications: make updatedAt public

* notifications: disable timestamps
2016-12-15 17:33:24 +01:00
Travis fa788f49fc Preventing users from buying already gifted subscriber items (#7734)
* Adding the unopened mystery items to the call to get not obtained subscriber items. closes #7712

* refactoring according to pr

* Refactoring according to pr. moved time-travelers to it's own file and added new tests.
2016-12-15 10:00:49 -06:00
Kaitlin Hipkin 0817cf96e1 Achievement list renovation & Achievements API (#7904)
* pull apart achievements into different subcategories

* achievs previously hidden to others if unachieved are now always shown

* achievs previously always hidden if unachieved are now always shown

* pull apart ultimate gear achievs

* add achiev wrapper mixin

* add achiev mixin for simple counts

* add achiev mixin for singular/plural achievs

* add simpleAchiev mixin and support attributes

* always hide potentially unearnable achievs if unearned

* contributor achiev now uses string interpolation for readMore link

* transition to basic achiev grid layout

* fix npc achievement img bug introduced in c90f7e2

* move surveys and contributor achievs into special section so it is never empty

* double size of achievs in achievs grid

* achievs in grid are muted if unachieved (includes recompiled sprites)

* fix streak notification strings

* add counts to achievement badges for applicable achieved achievs

* list achievements by api

* fix achievement strings in new api

* unearned achievs now use dedicated (WIP) 'unearned' badge instead of muted versions of the normal badges

* fix & cleanup achievements api

* extract generation of the achievements result to a class

* clean up achievement counter css using existing classes

* simplify exports of new achievementBuilder lib

* remove class logic from achievementBuilder lib

* move achievs to common, add rebirth achiev logic, misc fixes

* replace achievs jade logic with results of api call

* fix linting errors

* achievs lib now returns achievs object subdivided by type (basic/seasonal/special

* add tests for new achievs lib

* fix linting errors

* update controllers and views for updated achievs lib

* add indices to achievements to preserve intended order

* move achiev popovers to left

* rename achievs lib to achievements

* adjust positioning of achieve popovers now that stats and achievs pages
are separate

* fix: achievements api correctly decides whether to append extra string for master and triadBingo achievs

* revert compiled sprites so they don't bog down the PR

* pull out achievs api integration tests

* parameterize ultimate gear achievements' text string

* break out static achievement data from user-specific data

* reorg content.achievements to add achiev data in related chunks

* cleanup, respond to feedback

* improve api documentation

* fix merge issues

* Helped Habit Grow --> Helped Habitica Grow

* achievement popovers are muted if the achiev is unearned

* fix singular achievement labels / achievement popover on click

* update apidoc for achievements (description, param-type, successExample, error-types)

* fix whitespace issues in members.js

* move html to a variable

* updated json example

* fix syntax after merge
2016-12-13 12:48:18 -06:00
Matteo Pagliazzi 97e1d75dce 3.60.0 2016-12-12 22:04:34 +01:00
Matteo Pagliazzi 52bf20c27d upgrade shrinkwrap 2016-12-12 22:01:39 +01:00
Matteo Pagliazzi 5dbaf39aba Node 6 and NPM 4 (#8081)
* upgrade node to version 6

* upgrade npm to v4

* update shrinkwrap

* use npm 4 in travis

* use mongoose 4.6.4

* update shrinkwrap

* fix async test and upgrade mongoose

* fix amazon test

* remove debugging code

* working tests with separate server

* update coupon code

* mupdate mongoose

* nvm: relax node version in .nvmrc
2016-12-12 21:51:53 +01:00
Sabe Jones 66d402c985 3.59.1 2016-12-12 20:33:12 +00:00
Sabe Jones 8048146223 chore(news): Bailey 2016-12-12 20:17:58 +00:00
Matteo Pagliazzi e2c07e458d client: fix action name 2016-12-12 21:05:51 +01:00
padm0 90a9e8e192 Fixing 112016 mystery set. (#8276) 2016-12-12 09:13:59 -08:00
Keith Holliday f8039f48a6 Styled merch button to have highlight (#8273) 2016-12-10 22:09:06 -06:00
PowerlinxJetfire 04337f8e83 Fix filter buttons when windows resizes fixes (partially) #7772 (#8258)
* Reloads the quest panel solving issue #7697

* Revert "Reloads the quest panel solving issue #7697"

This reverts commit 0d58fb0fd3.

* fix overlapping filter buttons when windows resizes

This fixes one of the two causes of issue #7772.
https://github.com/HabitRPG/habitica/issues/7772
2016-12-10 22:08:26 -06:00
Keith Holliday 45297f8bf9 Merge pull request #8256 from a2lin/equipment_search
Adds a free-text filter (search) to the equipment page.
2016-12-10 16:28:11 -06:00
Keith Holliday 6f112c29f2 Merge pull request #8268 from Tallestthomas/develop
Show link leading to the Food Preferences Wiki page.
2016-12-10 14:41:17 -06:00
Keith Holliday 4d1edb363c Merge pull request #8243 from 15313-platypi/develop
Quest Panel Reload (Issue #7697)
2016-12-10 14:34:43 -06:00
Alexander Lin 4e303cc592 Clean up code 2016-12-10 02:15:30 -08:00
Travis 798a975185 fix: confirm no user objects reference a group before deleting it when the member count reaches 0 (#8267)
* fix: confirm no user objects reference a group before deleting it when the member count reaches 0

* Updating mongo queries to return promises and use the select statement.
2016-12-09 12:14:25 -08:00
Keith Holliday eb2b46fc5d Merge pull request #8269 from Hus274/8265
Adding a merchandise link to the marketplace selector
2016-12-09 11:57:36 -08:00
Keith Holliday 29854d3bdb Merge pull request #8271 from Hus274/8266
Adding habitica mugs to the static merchandise page
2016-12-09 11:54:45 -08:00
Sabe Jones f8751b002c fix(subs): record creation for gifts 2016-12-09 19:52:17 +00:00
Travis cd545e08d5 Implementing retries on failed user updates when finishing a quest (#8251)
* Implementing retries on failed user updates when finishing a quest. fixes #8035

* Refactoring mongo db retries to use the same as code path as original call and moving retries to count based over time based.

* Adding tests for retry logic and updating retries to happen recursively

* Moving callbacks to promises and other tweaks according to pr.

* Chaging mongoose promise to use .catch() functionality

* If all retries fail, the system will now throw an error instead of returning an error message.
2016-12-09 11:30:17 -08:00
Travis Husman f69bb4f023 Adding habitica mugs to the static/merch page. fixes #8266 2016-12-09 10:35:00 -08:00
Tom Rasmussen 847081d2b2 Removed extra newlines 2016-12-09 12:01:05 -05:00
Travis Husman 8112d46ea4 Removing line break 2016-12-09 07:32:40 -08:00
Travis Husman d13bded647 Updating the merchandise link to look like a list item. 2016-12-09 07:31:48 -08:00
Matteo Pagliazzi 1de4ab3612 client: namespaces for actions and getteters 2016-12-08 23:01:59 -08:00
Keith Holliday f9f22f313f Merge pull request #8261 from b9chris/develop
Fix a bug where 1005-768 the avatars and health bar get covered up.
2016-12-08 18:50:18 -08:00
Sabe Jones f57eed85a8 3.59.0 2016-12-09 02:42:13 +00:00
Sabe Jones 10dd3318ab fix(subs): append Gift for troubleshooting clarity 2016-12-09 02:35:51 +00:00
tallestthomas cbef83c14a Removed yarn.lock and added it to the .gitignore 2016-12-08 21:31:14 -05:00
Sabe Jones 59709a8590 chore(sprites): compile
and Bailey
2016-12-09 02:30:39 +00:00
Sabe Jones f85f2a0c6d Gift Subscriptions Promo (#8270)
* WIP(promo): buy-1-get-1 subs

* WIP(subscriptions): Slack integration

* feat(Slack): notify on sub buy
2016-12-08 18:08:56 -08:00
Travis Husman 605a5a1d5c Adding a merchandise link to the marketplace selector. fixes #8265 2016-12-08 08:19:13 -08:00
tallestthomas 2d5d786c8e Added pet food link to pets.json and jade template (issue #8023) 2016-12-08 11:05:22 -05:00
Travis 5efe5b7b10 updating dragon mount images to all align. fixes #8253 (#8259) 2016-12-08 19:52:12 +10:00
Alexander Lin 3e92bb22fa Refactor, add spec tests for equipment search 2016-12-08 00:08:18 -08:00
Sabe Jones 1249b9d410 feat(content): Dec 2016 pets 2016-12-07 20:57:25 +00:00
Keith Holliday 197aafe092 Updated the button stlyes to make them closer (#8260) 2016-12-07 11:47:24 -08:00
tallestthomas 79829ca128 Added link to pet food wiki (#8023) 2016-12-07 10:30:29 -05:00
Chris Moschini adaa1d9a3e Fix a bug where 1005-768 the avatars and health bar get covered up. 2016-12-07 08:55:36 -05:00
Matteo Pagliazzi 3e6691dbbb client: test: getters 2016-12-06 18:53:03 -08:00
Matteo Pagliazzi 046761b9aa client: reorganize filters and add tests 2016-12-06 18:27:49 -08:00
Matteo Pagliazzi 0b0466b960 client: reorganize actions 2016-12-06 17:11:40 -08:00
Matteo Pagliazzi f8d4a2bd6b client: update blue and yellow colors 2016-12-06 14:05:01 -08:00
Matteo Pagliazzi 1af59a3770 client: move files again to fix tests 2016-12-06 13:17:23 -08:00
Matteo Pagliazzi bbcb13c91b client: reorganize store files 2016-12-06 12:47:47 -08:00
Matteo Pagliazzi d27dc46c50 chore(i18n): update locales 2016-12-05 16:13:45 -08:00
Alexander Lin 679459b83b Adds free-text filter to equipment page
Closes #8241
2016-12-05 01:06:56 -08:00
Blade Barringer 5a619773d5 chore(i18n): update locales 2016-12-05 01:12:42 -06:00
Marcel Oyuela-Bonzani ad76ab1315 Triple Equals sign 2016-12-04 13:40:46 -05:00
Marcel Oyuela-Bonzani 15eb8db925 Fix for issue noted. 2016-12-04 04:31:04 -05:00
MathWhiz a0e92c5605 change stable text (#8247) 2016-12-04 17:37:23 +10:00
Chris eac3e36c07 changed favicon - fixes #7720 (#8252)
* changed favicon

* Revert "changed favicon"

This reverts commit f28b9eb738.

* Changed Favicon

 fixes #7720
2016-12-04 17:30:35 +10:00
Alys 0b8def555b make default profile name more descriptive (ref https://github.com/HabitRPG/habitica/commit/dca958f2e23f38492fd9abbe426265aae4c71dd4) 2016-12-04 13:43:49 +10:00
Alys 5f5fa5c2eb rename Library of Shared Lists guild to Library of Tasks and Challenges 2016-12-04 12:49:43 +10:00
Sabe Jones 1eac8bbbbe fix(migration): correct comments 2016-12-02 09:48:26 -06:00
Alys 49c7580cd4 3.58.1 2016-12-02 19:41:31 +10:00
Matteo Pagliazzi dca958f2e2 make sure entire user is loaded when saved 2016-12-02 10:20:54 +01:00
Sabe Jones eae5f0d605 fix(sprites): staff position 2016-12-02 00:08:44 +00:00
Sabe Jones 6ab091645c 3.58.0 2016-12-01 21:44:23 +00:00
Sabe Jones d66041c280 chore(sprites): compile 2016-12-01 21:43:46 +00:00
Sabe Jones de070a450a feat(content): BGs and Armoire 2016-12 2016-12-01 21:22:22 +00:00
Sabe Jones eaaab35f31 fix(stats): back & body works
Also adds December Take This migration
2016-12-01 18:49:40 +00:00
Travis 6a63f080ad New feature that notifies a user when their group invite is accepted. (#8244)
* New notification feature that notifies a user when their group invite is accepted. fixes #7788

* Updating to a modal instead of a popup notification

* Making a generic modal template and using it for notifications of group invitation acceptance.

* Working with paglias's comments for doing translation server side.

* Final changes based on pr comments.
2016-12-01 19:04:57 +01:00
Myles Louis Dakan c42f81b629 changed quest images for egg and knight1 (#8245) 2016-12-01 10:58:38 -06:00
Sabe Jones 9a78a7b896 fix(npcs): remove foamy Daniel 2016-12-01 15:44:08 +00:00
Alys 8b70721137 change date for latest Bailey from 11/24 to 11/30 2016-12-01 20:51:36 +10:00
Alys 44ffbd716d make gem cap reset message more accurate for when you're reading it at the start of a month 2016-12-01 20:15:13 +10:00
Sabe Jones 5bfc3a5ff4 3.57.2 2016-11-30 21:07:17 +00:00
Sabe Jones 0ba5df4164 chore(news): Last Chance Bailey 2016-11-30 20:43:55 +00:00
Sabe Jones 52a59c8192 Revert "Display first login incentive reward when bailey is dismissed (#8234)"
This reverts commit ac732b2c85.
2016-11-30 20:42:39 +00:00
Matteo Pagliazzi c1a860494d chore(i18n): update locales 2016-11-30 14:08:34 +01:00
Keith Holliday 395dafa127 Merge pull request #8242 from TheHollidayInn/login-incentives-remove-multiple-notifications
Login incentives remove multiple notifications
2016-11-29 09:51:34 -06:00
Keith Holliday bab41647f5 Fixed lint issues 2016-11-29 09:18:07 -06:00
Keith Holliday 8582a67308 Fixed broken tests and style changes 2016-11-29 08:53:36 -06:00
Marcel Oyuela-Bonzani 0d58fb0fd3 Reloads the quest panel solving issue #7697 2016-11-28 22:29:20 -05:00
Keith Holliday 1d2482f8bc Fixed linting issues 2016-11-28 21:24:25 -06:00
Keith Holliday f4cf906127 Added remove when previous login incentive notifications exist 2016-11-28 21:19:53 -06:00
Sabe Jones 559f9b1825 3.57.1 2016-11-28 21:40:34 +00:00
Sabe Jones c7039bc9ea fix(incentives): pixel paws, purple background
Also turns off automatic Base Turkey pet award for new users.
2016-11-28 20:57:35 +00:00
Matteo Pagliazzi f929d36e1a chore(i18n): update locales 2016-11-28 19:43:06 +01:00
Keith Holliday 254d1a3465 Merge pull request #8237 from TheHollidayInn/login-progress-counter-fix
Fixed login counter on the first day
2016-11-26 17:36:01 -06:00
Alys 442aae8a35 fix spelling mistake in Check-In Incentives social media messages (consitent > consistent) 2016-11-27 07:29:47 +10:00
Keith Holliday bcb0ed0a5c Fixed login counter on the first day 2016-11-26 10:30:09 -06:00
Alys a48b8f0e34 add whitespace between GP and XP amount and label in quest modals 2016-11-26 18:32:37 +10:00
Alyssa Batula 7eeeda2aae Send a message to the party chat when a quest is aborted, fixes #4879 (#8150)
* Send a message to the party chat when a quest is aborted

* Added test cases for sending a message to party when quest is aborted

* Restore Group.prototype.sendChat after aborted quest test
2016-11-26 17:48:42 +10:00
Sabe Jones a5ad9c30f0 fix(mobile): temp remove new unlock styles 2016-11-24 06:06:11 +00:00
Keith Holliday ac732b2c85 Display first login incentive reward when bailey is dismissed (#8234) 2016-11-23 21:44:11 -06:00
Sabe Jones a56b2d68fb 3.57.0 2016-11-24 02:05:03 +00:00
Sabe Jones 25b0ff38c4 Login Incentives (#8230)
* feat(incentives): login bennies WIP

* feat(content): incentive prize content WIP

* fix(content): placeholders pass tests

* WIP(content): Bard instrument placeholder

* feat(content): Incentives build

* chore(sprites): compile
and fix some strings

* WIP(incentives): quests and backgrounds

* fix(quests): correct buy/launch handling

* [WIP] Incentives rewarding (#8226)

* Added login incentive rewards

* Updated incentive rewards

* Added incentive modal and updated notification structure

* Added analytics to sleeping

* Added login incentives to user analytics

* Fixed unit tests and ensured that prizes are incremented and not replaced

* Updated style of daily login incentive modal

* Added rewards modal

* Added translations

* Added loigin incentive ui elements to profile

* Updated login incentives structure and abstracted to common.content

* Added dynamic display for login incentives on profile

* Added purple potion image

* Updated daily login modal

* Fixed progress calculation

* Added bard gear

* Updated login incentive rewards

* Fixed styles and text

* Added multiple read for notifications

* Fixed lint issues

* Fixed styles and added 50 limit

* Updated quest keys

* Added login incentives reward page

* Fixed tests

* Fixed linting and tests

* Read named notifications route. Add image for backgrounds

* Fixed style issues and added tranlsations to login incentive notification

* Hided abiltiy to purchase incentive backgrounds and added message to detail how to unlock

* Updated awarded message

* Fixed text and updated progress counter to display better

* Fixed purple potion reward text

* Fixed check in backgrouns reward text

* fix(quest): pass tests

* Added display of multiple rewards

* Updated modal styles

* Fixed neagtive 50 issue

* Remvoed total count from daily login incentives modal

* Fixed magic paw display

* fix(awards): give bunnies again

* WIP(incentives): more progress on BG shop

* fix(incentives): actually award backgrounds

* fix(incentives): more BG fixy

* fix(backgrounds): don't gem-buy checkin bgs

* Added dust bunny notification

* fix(incentives): don't redisplay bunny award

* chore(news): Bailey
and different promo sprite
2016-11-23 19:34:09 -06:00
Sabe Jones dcc06931cc fix(test): disable unreliable XML test 2016-11-23 23:16:16 +00:00
Matteo Pagliazzi bc3ebbd095 pin mongoose 2016-11-23 21:22:35 +01:00
Matteo Pagliazzi e5b9581743 update vue 2016-11-23 20:02:35 +01:00
Matteo Pagliazzi 4b9fe49e3a skip randomly failing test 2016-11-23 14:46:23 +01:00
Alys ab4c8b0a46 delete broken newsArchive translation and link from Whats New page to fix https://github.com/HabitRPG/habitica/pull/8216#issuecomment-262430401 2016-11-23 21:39:14 +10:00
Sabe Jones f6c26fe869 3.56.0 2016-11-23 02:05:23 +00:00
Sabe Jones 80e9735b28 Turkey Day 2016 (#8231)
* feat(event): Turkey Day 2016

* fix(test): allow for free pet
2016-11-22 20:00:10 -06:00
Matteo Pagliazzi aa6f188bd9 new client: remove comments 2016-11-22 13:49:35 +01:00
MathWhiz e8b7660376 Add Costume Info to member modal (#7768)
* Add localization strings

* Change name of Equipment section

* Add costume section to member modal

* Add costume section to member modal

* Add current pet and current mount info

* Reorder Sections and Separate Active Mounts/Pets

* switch ng-show with ng-if

* Add `noActiveMount` to pets.json

* Breaking Stuff

* Add petservices.js to the manifest

* Remove Extra Parenthesis

* Progress towards backgrounds

* Add semicolons

* Add background information

* Add all methods in petServices to userCtrl and memberModalCtrl

* Add avatar settings

* Add semicolons

* Revert "Add avatar settings"

This reverts commit 6e8cca9736.

* Remove active-pet-and-mount

* Remove Content from memberModalCtrl

* Update costumeServices.js

* Make costumeservices.js more readable

* Update costumeServices.js

* Update costumeService logic

* Remove unused strings

* Fix include statements

* move service

* Update pet/mount logic

* fixes

* Fix background logic
2016-11-21 21:19:13 +10:00
Mich Elliott 7d76622410 Remove white backgrounds from mount sprites (#8217)
* Remove white background from single mount sprite

* Remove white background on ~40 mount sprites
2016-11-21 07:48:41 +10:00
MathWhiz 928e5f66c4 Add translation link to news (#8216)
* Add translation link to news

* Add newsArchive string

* Translate news archive link
2016-11-20 14:22:59 +10:00
MathWhiz 6a343535c0 Remove party joined option (#8212)
* Remove party joined option

* Make default sort sort by profile name

* Remove extraneous comment
2016-11-20 14:08:18 +10:00
Alys f58f6acb44 correct apidoc comments for updating and deleting a tag 2016-11-19 12:37:21 +10:00
Matteo Pagliazzi 64754777ed New Client: working navigation (#8131)
* initial work

* new client: working navigation and tasks showing up

* finish header menu and add avatar component

* fix sprites in new client

* initial header version

* initial styling for top menu

* more progress on the header menu

* almost complete menu and avatar

* correctly apply active class for /social and /help

* fix header colors and simplify css

* switch from Roboto to native fonts

* remove small avatar and add viewport

* fixes

* fix user menu with and progress bars

* fix avatar rendeting

* move bars colors to theme

* add site overrides

* fix tests

* shrinkwrap

* fix sprites path

* another try at fixing the sprites path

* another try at fixing the sprites path
2016-11-18 19:20:25 +01:00
Sabe Jones 3b5e4b6d84 3.55.0 2016-11-17 22:26:26 +00:00
Sabe Jones 9383578cb8 chore(news): Bailey 2016-11-17 21:29:26 +00:00
Sabe Jones 474672ec64 Merge pull request #8225 from HabitRPG/sabrecat/hairstyles
New Hairstyles
2016-11-17 15:20:55 -06:00
Keith Holliday 25c6691793 Added party sync and request sync events (#8223)
* Added party sync and request sync events

* Changed party member sync to be handled locally

* Optimized assignment to only use member variables

* Removed party sync event
2016-11-17 20:10:33 +01:00
Keith Holliday 3ea7b72024 Passed language param to text functions (#8220) 2016-11-17 19:32:07 +01:00
Sabe Jones 2d6f05a9a4 fix(hairstyles): base layer above bangs 2016-11-17 17:49:35 +00:00
Sabe Jones 28637286d6 fix(sprites): remove base outlines 2016-11-17 17:49:35 +00:00
Sabe Jones 874887b790 fix(hair): exclusivity and canvas tweaks 2016-11-17 17:49:34 +00:00
Sabe Jones c977e5ebb5 fix(sprites): adjust Y position 2016-11-17 17:49:34 +00:00
Sabe Jones f040e668f3 chore(sprites): add 2016-11-17 17:49:34 +00:00
Sabe Jones 55a15f938c feat(customize): new hairstyles 2016-11-17 17:45:31 +00:00
shalott 8c4f35daf4 Fixing test failure
This test seems to occasionally start failing (another coder reported the same thing happening to them in the blacksmiths’ guild) because the order in which the tasks are created can sometimes not match the order in the array. So I have sorted the tasks array after creation by the task name to ensure a consistent ordering, and slightly reordered the expect statements to match.
2016-11-16 21:52:23 +01:00
Matteo Pagliazzi 8f38ce3424 do not give _id to purchased.plan 2016-11-16 21:44:47 +01:00
Arashi007 b8f57a74d0 Added Airu's Theme (#8204)
* Airu's Theme
* Delete Minus_Habit.ogg
* Delete Minus_Habit.mp3
* Add files via upload
2016-11-16 20:11:22 +10:00
Sabe Jones 7ed26c0dbe 3.54.0 2016-11-16 02:12:33 +00:00
Sabe Jones e8f5b26d4d chore(sprites): compile
and Bailey
2016-11-16 02:04:10 +00:00
Sabe Jones 0273648b6b Merge pull request #8221 from HabitRPG/sabrecat/pets-201611
Ferret Pet
2016-11-15 19:48:33 -06:00
Sabe Jones b6fdac8885 feat(quest): Ferret Pet 2016-11-15 22:14:12 +00:00
Sabe Jones 00e6389672 3.53.5 2016-11-15 04:21:46 +00:00
Blade Barringer e02c669b61 Move hr to prevent UserID comment from showing (#8214) 2016-11-14 21:59:31 -06:00
Blade Barringer f0cb7c6bf3 Comment out group task fetching 2016-11-14 21:42:23 -06:00
Sabe Jones 571ef0b309 fix(news): add date 2016-11-15 03:34:50 +00:00
Blade Barringer 74328d1bcc chore(i18n): update locales 2016-11-14 21:33:01 -06:00
Sabe Jones d34a9d828c Removed task get approvals request (#8218) 2016-11-14 21:09:36 -06:00
Keith Holliday 2fd35b3a40 Removed task get approvals request 2016-11-14 21:05:54 -06:00
Sabe Jones e27512f626 3.53.4 2016-11-15 00:46:15 +00:00
Sabe Jones dbf9cb3b4e chore(news): misc Bailey 2016-11-15 00:27:40 +00:00
AccioBooks 34c1245519 Move hr to prevent UserID comment from showing 2016-11-14 10:58:03 -06:00
Keith Holliday f602bfe438 Removed group subscription options (#8211) 2016-11-13 20:24:59 +01:00
Alys 9aa4b8aa64 add 'month' to gift subscription message - fixes https://github.com/HabitRPG/habitica/issues/7747
I haven't pluralised this by using "month(s)", because the phrase "a 3 month subscription" is acceptable in English. Translators may use pluralisation as desired, although note that the same wording will be used for 1 month subscriptions.
2016-11-13 20:40:01 +10:00
Alys 5a150ebc5b change '/group/' to '/groups/' in docs for /api/v3/challenges/groups/:groupId 2016-11-13 17:43:52 +10:00
Keith Holliday cbe1892b50 Added note sync when user adds task to challenge, tests, and fixed challenge tests (#8200) 2016-11-12 23:48:22 +01:00
Keith Holliday 13df60e0dd Group approval ui (#8184)
* Added all ui components back

* Added group ui items back and initial group approval directive

* Added ability to mark tasks as requires approval. Added approvals ctrl. Added get approvals method to tasks service

* Added approval list view with approving functionality

* Added error to produce message when task requests approval

* Added notification display for group approvals

* Fixed notification read and adding task

* Fixed syncing with group approval required

* Added group id to notifications for redirect on client side

* Fixed approval request tests

* Fixed linting issues

* Removed expectation from beforeEach

* Moved string to locale

* Added eslint ignore

* Updated notification for group approved, added new icons, and updated styles

* Hid group plan ui
2016-11-12 23:47:45 +01:00
Blade Barringer 3ff7692528 chore(i18n): update locales 2016-11-11 08:08:46 -06:00
Sabe Jones 111bba84dc feat(content): 2016-11 pet quest strings 2016-11-10 23:12:42 +00:00
Keith Holliday b0d2b72b88 Updated buy special item to use function call wrapper (#8203) 2016-11-10 21:36:49 +01:00
Sabe Jones 696317ea8a fix(quests): Basilist error with no party 2016-11-07 15:30:44 +00:00
Sabe Jones 593178a46a fix(sprites): copy corrected Ian to prod path
fixes #7867 (again)
2016-11-07 15:20:04 +00:00
MathWhiz f8fe16482d Unsubscribe documentation
closes #8187
2016-11-06 21:41:12 -06:00
Romeeka Gayhart 5108480ec5 Get skipped/pending unit tests working for revive (#8193) 2016-11-06 21:17:52 -06:00
Sabe Jones 95968b1b1c 3.53.3 2016-11-06 22:05:28 +00:00
Sabe Jones 566569af98 fix(event): end Fall Fest f'real 2016-11-06 21:36:52 +00:00
Alys 6693e9fca9 replace candy food with normal food and enhance canBuy / canDrop code (#8194)
* change food to normal; add variables to choose type of food; add canBuy, canDrop to cake

* reinstate ability to control canBuy and canDrop separately
2016-11-06 15:33:19 -06:00
Romeeka Gayhart 431bde56d2 Convert test UUID to string to avoid test error (#8195) 2016-11-05 23:53:11 -04:00
Sabe Jones 7cf17c0e63 3.53.2 2016-11-04 21:15:10 +00:00
Sabe Jones 49561bfc8c fix(test): accommodate changing seasons 2016-11-04 20:38:29 +00:00
Sabe Jones 8cbbb58e78 chore(event): end Fall Fest 2016-11-04 20:20:53 +00:00
Sabe Jones 905549e379 3.53.1 2016-11-04 19:23:55 +00:00
Sabe Jones 5d45c7209a chore(news): blog Bailey 2016-11-04 19:02:29 +00:00
Rick Kasten 371cddfe17 Updated bossColl1, bossColl1Broken (#8148) 2016-11-04 19:38:12 +10:00
AccioBooks fcfac30caa Api doc status (#8165)
* Add example

* Update example
2016-11-03 08:31:30 -05:00
Corinna Jaschek b094fb1e52 added message for challenges that could not be found - fixes #5538
closes #8176
2016-11-03 07:52:43 -05:00
Keith Holliday a2dd82b6db Hid nav bar (#8181) 2016-11-02 21:58:17 -05:00
Sabe Jones e6071610e4 fix(migration): revert bogus connect info 2016-11-03 00:29:57 +00:00
Sabe Jones bdd0e2bb79 3.53.0 2016-11-03 00:12:32 +00:00
Sabe Jones 054a9a6f2b chore(sprites): compile 2016-11-02 23:29:12 +00:00
Sabe Jones 35b9ed6273 backgrounds and Armoire 2016-11 (#8178)
* feat(content): backgrounds and Armoire 2016-11

* chore(event): November Take This migration

* chore(news): Bailey
2016-11-02 18:27:32 -05:00
Keith Holliday e65277baa5 Added check to ensure config is defined (#8180) 2016-11-02 18:27:22 -05:00
Amanda Furrow 421bd8624c Add flagger language to flag message sent to slack
closes #8179
fixes #8140
2016-11-02 17:28:44 -05:00
Blade Barringer 4562c6422a chore(i18n): update locales 2016-11-02 17:20:46 -05:00
Matteo Pagliazzi a5cd9f2473 Merge branch 'TheHollidayInn-group-tasks-approval2' into develop 2016-11-01 21:55:32 +01:00
Matteo Pagliazzi 18bbdfa84b Merge branch 'group-tasks-approval' of https://github.com/TheHollidayInn/habitrpg into TheHollidayInn-group-tasks-approval2 2016-11-01 21:55:18 +01:00
Keith Holliday d8c37f6e2d Group plan subscription (#8153)
* Added payment to groups and pay with group plan with Stripe

* Added edit card for Stripe

* Added stripe cancel

* Added subscribe with Amazon payments

* Added Amazon cancel for group subscription

* Added group subscription with paypal

* Added paypal cancel

* Added ipn cancel for Group plan

* Added a subscription tab and hid only the task tab when group is not subscribed

* Fixed linting issues

* Fixed tests

* Added payment unit tests

* Added back refresh after stripe payment

* Fixed style issues

* Limited grouop query fields and checked access

* Abstracted subscription schema

* Added year group plan and more access checks

* Maded purchase fields private

* Removed id and timestampes

* Added else checks to ensure user subscription is not altered. Removed active field from group model

* Added toJSONTransform function

* Moved plan active check to other toJson function

* Added check to see if purchaed has been populated

* Added purchase details to private

* Added correct data usage when paying for group sub
2016-11-01 21:51:30 +01:00
Sabe Jones 7f38c61c70 3.52.0 2016-10-31 19:02:24 +00:00
Sabe Jones 1c018cedb1 chore(event): sprites and news 2016-10-31 18:45:39 +00:00
Sabe Jones 80892bd6a8 feat(event): JackOLantern ladder (#8174) 2016-10-31 08:14:06 -05:00
Keith Holliday 6801dae75d Fixed history test 2016-10-30 03:23:01 -05:00
Keith Holliday 59e1de6771 Moved approval to subdoc 2016-10-29 14:19:16 -05:00
Keith Holliday 5b240a1950 Updated notification name and other minor fixes 2016-10-29 14:19:16 -05:00
Keith Holliday 3ec3722038 Moved approval fields to group subdoc 2016-10-29 14:19:16 -05:00
Keith Holliday d798ebadfe Fixed line endings 2016-10-29 14:19:16 -05:00
Keith Holliday 6cbddef627 Added get approvals route 2016-10-29 14:19:16 -05:00
Keith Holliday 016de411c9 Added notifications 2016-10-29 14:19:16 -05:00
Keith Holliday 2173f53883 Added fields for more approver details 2016-10-29 14:19:15 -05:00
Keith Holliday f2e5bc52e5 Added requested approval fields and logic 2016-10-29 14:19:15 -05:00
Keith Holliday 393a9290e9 Added approval test and fixed line endings 2016-10-29 14:19:15 -05:00
Keith Holliday ad5045bc09 Added git score approved task test 2016-10-29 14:19:15 -05:00
Keith Holliday 9b515ebdd1 Added task approve route 2016-10-29 14:19:15 -05:00
Keith Holliday 97bf9ee8e8 Added inital group task approval 2016-10-29 14:19:15 -05:00
Blade Barringer f5ba636579 chore(i18n): update locales 2016-10-27 22:03:58 -05:00
Sabe Jones 4dd7e49552 3.51.1 2016-10-27 20:44:17 +00:00
Sabe Jones d2f673ef1e chore(news): Blog Bailey 2016-10-27 19:58:43 +00:00
Sabe Jones e198dd551a feat(content): strings for BGs/Armoire 2016-11 2016-10-26 20:28:44 +00:00
Travis 0bfc9d9516 fix: allows user to save an alias and checklistCollapsed properties of a challenge task. fixes #7875 (#8170) 2016-10-25 21:47:49 -05:00
Sabe Jones d4e20ee4aa 3.51.0 2016-10-25 21:56:09 +00:00
Sabe Jones a751a367fc chore(sprites): compile 2016-10-25 21:55:40 +00:00
Sabe Jones d323be19c6 Mystery Items 2016/10 (#8169)
* feat(content): mystery items 2016-10

* chore(news): Bailey 2016-10-25
Also ends the Enchanted Armoire A/B test.

* fix(armoire): failing tests from A/B conclusion
2016-10-25 16:16:00 -05:00
AccioBooks be3f61a94b Remove cookies on clearing browser data (#8135)
* remove cookies

* update cookie removal

* Remove + and add link

* Fix tests

* Add condition

* update strings
2016-10-25 19:53:56 +10:00
Alys f1bb2db73b fix wrong variable name in Polish questDamage string
The translators have been notified that it needs to be fixed in Transifex before the next migration of strings back to GitHub.
2016-10-23 09:54:18 +10:00
2768 changed files with 115945 additions and 40181 deletions
+3
View File
@@ -0,0 +1,3 @@
node_modules
.git
website
+1 -1
View File
@@ -14,7 +14,7 @@ files:
owner: root
group: users
content: |
$(ls -td /opt/elasticbeanstalk/node-install/node-* | head -1)/bin/npm install -g npm@3
$(ls -td /opt/elasticbeanstalk/node-install/node-* | head -1)/bin/npm install -g npm@4
container_commands:
01_makeBabel:
command: "touch /tmp/.babel.json"
+1 -5
View File
@@ -20,8 +20,4 @@ website/common/browserify.js
test/content/**/*
Gruntfile.js
gulpfile.js
gulp
webpack
test/client/e2e
test/client/unit/index.js
test/client/unit/karma.conf.js
gulp
+2 -2
View File
@@ -4,7 +4,7 @@
"node": true,
},
"extends": [
"habitrpg/es6",
"habitrpg"
"habitrpg",
"habitrpg/esnext"
],
}
+1
View File
@@ -9,5 +9,6 @@ Fixes put_issue_url_here
[//]: # (Put User ID in here - found in Settings -> API)
----
UUID:
+1 -1
View File
@@ -1 +1 @@
4.3.1
6
+13 -2
View File
@@ -1,8 +1,16 @@
language: node_js
node_js:
- '4.3.1'
- '6'
sudo: required
addons:
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- g++-4.8
before_install:
- npm install -g npm@3
- $CXX --version
- npm install -g npm@4
- if [ $REQUIRES_SERVER ]; then sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 7F0CEB10; echo 'deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen' | sudo tee /etc/apt/sources.list.d/mongodb.list; sudo apt-get update; sudo apt-get install mongodb-org-server; fi
before_script:
- npm run test:build
@@ -12,6 +20,9 @@ after_script:
- ./node_modules/.bin/lcov-result-merger 'coverage/**/*.info' | ./node_modules/coveralls/bin/coveralls.js
script: npm run $TEST
env:
global:
- CXX=g++-4.8
- DISABLE_REQUEST_LOGGING=true
matrix:
- TEST="lint"
- TEST="test:api-v3" REQUIRES_SERVER=true
+6 -4
View File
@@ -17,20 +17,22 @@ RUN apt-get install -y \
python
# Install NodeJS
RUN curl -sL https://deb.nodesource.com/setup_4.x | sudo -E bash -
RUN curl -sL https://deb.nodesource.com/setup_6.x | sudo -E bash -
RUN apt-get install -y nodejs
# Install npm@latest
RUN curl -sL https://www.npmjs.org/install.sh | sh
# Clean up package management
RUN apt-get clean
RUN rm -rf /var/lib/apt/lists/*
# Install global packages
RUN npm install -g npm@3
RUN npm install -g gulp grunt-cli bower
RUN npm install -g gulp grunt-cli bower mocha
# Clone Habitica repo and install dependencies
WORKDIR /habitrpg
RUN git clone https://github.com/HabitRPG/habitrpg.git /habitrpg
RUN git clone https://github.com/HabitRPG/habitica.git /habitrpg
RUN npm install
RUN bower install --allow-root
+2 -1
View File
@@ -57,7 +57,7 @@ module.exports = function(grunt) {
files: [
{expand: true, cwd: 'website/client-old/', src: 'favicon.ico', dest: 'website/build/'},
{expand: true, cwd: 'website/client-old/', src: 'favicon_192x192.png', dest: 'website/build/'},
{expand: true, cwd: 'website/assets/sprites/dist/', src: 'spritesmith*.png', dest: 'website/build/'},
{expand: true, cwd: 'website/assets/sprites/dist/', src: 'spritesmith*.png', dest: 'website/build/static/sprites'},
{expand: true, cwd: 'website/assets/sprites/', src: 'backer-only/*.gif', dest: 'website/build/'},
{expand: true, cwd: 'website/assets/sprites/', src: 'npc_ian.gif', dest: 'website/build/'},
{expand: true, cwd: 'website/assets/sprites/', src: 'quest_*.gif', dest: 'website/build/'},
@@ -78,6 +78,7 @@ module.exports = function(grunt) {
'website/build/favicon.ico',
'website/build/favicon_192x192.png',
'website/build/*.png',
'website/build/static/sprites/*.png',
'website/build/*.gif',
'website/build/bower_components/bootstrap/dist/fonts/*'
],
+1 -1
View File
@@ -1,4 +1,4 @@
Habitica [![Build Status](https://travis-ci.org/HabitRPG/habitica.svg?branch=develop)](https://travis-ci.org/HabitRPG/habitica) [![Code Climate](https://codeclimate.com/github/HabitRPG/habitrpg.svg)](https://codeclimate.com/github/HabitRPG/habitrpg) [![Coverage Status](https://coveralls.io/repos/HabitRPG/habitrpg/badge.svg?branch=develop)](https://coveralls.io/r/HabitRPG/habitrpg?branch=develop) [![Bountysource](https://api.bountysource.com/badge/tracker?tracker_id=68393)](https://www.bountysource.com/trackers/68393-habitrpg?utm_source=68393&utm_medium=shield&utm_campaign=TRACKER_BADGE)
Habitica [![Build Status](https://travis-ci.org/HabitRPG/habitica.svg?branch=develop)](https://travis-ci.org/HabitRPG/habitica) [![Code Climate](https://codeclimate.com/github/HabitRPG/habitrpg.svg)](https://codeclimate.com/github/HabitRPG/habitrpg) [![Coverage Status](https://coveralls.io/repos/github/HabitRPG/habitica/badge.svg?branch=develop)](https://coveralls.io/github/HabitRPG/habitica?branch=develop) [![Bountysource](https://api.bountysource.com/badge/tracker?tracker_id=68393)](https://www.bountysource.com/trackers/68393-habitrpg?utm_source=68393&utm_medium=shield&utm_campaign=TRACKER_BADGE)
===============
[Habitica](https://habitica.com) is an open source habit building program which treats your life like a Role Playing Game. Level up as you succeed, lose HP as you fail, earn money to buy weapons and armor.
+11 -4
View File
@@ -2,13 +2,18 @@
"PORT":3000,
"ENABLE_CONSOLE_LOGS_IN_PROD":"false",
"IP":"0.0.0.0",
"CORES":1,
"WEB_CONCURRENCY":1,
"BASE_URL":"http://localhost:3000",
"FACEBOOK_ANALYTICS":"1234567890123456",
"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",
@@ -79,6 +84,8 @@
},
"SLACK": {
"FLAGGING_URL": "https://hooks.slack.com/services/id/id/id",
"FLAGGING_FOOTER_LINK": "https://habitrpg.github.io/flag-o-rama/"
}
"FLAGGING_FOOTER_LINK": "https://habitrpg.github.io/flag-o-rama/",
"SUBSCRIPTIONS_URL": "https://hooks.slack.com/services/id/id/id"
},
"ITUNES_SHARED_SECRET": "aaaabbbbccccddddeeeeffff00001111"
}
+1 -1
View File
@@ -1,3 +1,3 @@
web:
volumes:
- '.:/habitrpg'
- '.:/habitrpg'
+4 -4
View File
@@ -1,13 +1,13 @@
web:
build: .
ports:
- "3000:3000"
- "3000:3000"
links:
- mongo
- mongo
environment:
- NODE_DB_URI=mongodb://mongo/habitrpg
- NODE_DB_URI=mongodb://mongo/habitrpg
mongo:
image: mongo
ports:
- "27017:27017"
- "27017:27017"
+12 -2
View File
@@ -12,6 +12,9 @@ import {each} from 'lodash';
const MAX_SPRITESHEET_SIZE = 1024 * 1024 * 3;
const DIST_PATH = 'website/assets/sprites/dist/';
const IMG_DIST_PATH_NEW_CLIENT = 'website/static/sprites/';
const CSS_DIST_PATH_NEW_CLIENT = 'website/client/assets/css/sprites/';
gulp.task('sprites:compile', ['sprites:clean', 'sprites:main', 'sprites:largeSprites', 'sprites:checkCompiledDimensions']);
gulp.task('sprites:main', () => {
@@ -25,7 +28,7 @@ gulp.task('sprites:largeSprites', () => {
});
gulp.task('sprites:clean', (done) => {
clean(`${DIST_PATH}spritesmith*`, done);
clean(`{${DIST_PATH}spritesmith*,${IMG_DIST_PATH_NEW_CLIENT}spritesmith*,${CSS_DIST_PATH_NEW_CLIENT}spritesmith*}`, done);
});
gulp.task('sprites:checkCompiledDimensions', ['sprites:main', 'sprites:largeSprites'], () => {
@@ -66,14 +69,16 @@ function createSpritesStream (name, src) {
algorithm: 'binary-tree',
padding: 1,
cssTemplate: 'website/assets/sprites/css/css.template.handlebars',
cssVarMap: cssVarMap
cssVarMap: cssVarMap,
}));
let imgStream = spriteData.img
.pipe(imagemin())
.pipe(gulp.dest(IMG_DIST_PATH_NEW_CLIENT))
.pipe(gulp.dest(DIST_PATH));
let cssStream = spriteData.css
.pipe(gulp.dest(CSS_DIST_PATH_NEW_CLIENT))
.pipe(gulp.dest(DIST_PATH));
stream.add(imgStream);
@@ -148,4 +153,9 @@ function cssVarMap (sprite) {
}
if (~sprite.name.indexOf('shirt'))
sprite.custom.px.offset_y = `-${ sprite.y + 30 }px`; // even more for shirts
if (~sprite.name.indexOf('hair_base')) {
let styleArray = sprite.name.split('_').slice(2,3);
if (Number(styleArray[0]) > 14)
sprite.custom.px.offset_y = `-${ sprite.y }px`; // don't crop updos
}
}
+1 -1
View File
@@ -318,7 +318,7 @@ gulp.task('test:api-v3:integration:watch', () => {
gulp.task('test:api-v3:integration:separate-server', (done) => {
let runner = exec(
testBin('mocha test/api/v3/integration --recursive', 'LOAD_SERVER=0'),
testBin('mocha test/api/v3/integration --recursive --require ./test/helpers/start-server', 'LOAD_SERVER=0'),
{maxBuffer: 500 * 1024},
(err, stdout, stderr) => done(err)
);
+2 -2
View File
@@ -84,8 +84,8 @@ gulp.task('transifex:malformedStrings', () => {
let malformedString = `${lang} - ${file} - ${key} - ${translationString}`;
stringsWithMalformedInterpolations.push(malformedString);
} else if (englishOccurences.length !== translationOccurences.length && !malformedStringExceptions[key]) {
let missingInterploationString = `${lang} - ${file} - ${key} - ${translationString}`;
stringsWithIncorrectNumberOfInterpolations.push(missingInterploationString);
let missingInterpolationString = `${lang} - ${file} - ${key} - ${translationString}`;
stringsWithIncorrectNumberOfInterpolations.push(missingInterpolationString);
}
});
});
+86
View File
@@ -0,0 +1,86 @@
var migrationName = '20161030-jackolanterns.js';
var authorName = 'Sabe'; // in case script author needs to know when their ...
var authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; //... own data is done
/*
* set the newStuff flag in all user accounts so they see a Bailey message
*/
var mongo = require('mongoskin');
var connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
var dbUsers = mongo.db(connectionString).collection('users');
// specify a query to limit the affected users (empty for all users):
var query = {
'auth.timestamps.loggedin':{$gt:new Date('2016-10-01')} // remove when running migration a second time
};
// specify fields we are interested in to limit retrieved data (empty if we're not reading data):
var fields = {
'migration': 1,
'items.pets.JackOLantern-Base': 1,
'items.mounts.JackOLantern-Base': 1,
};
console.warn('Updating users...');
var progressCount = 1000;
var count = 0;
dbUsers.findEach(query, fields, {batchSize:250}, function(err, user) {
if (err) { return exiting(1, 'ERROR! ' + err); }
if (!user) {
console.warn('All appropriate users found and modified.');
setTimeout(displayData, 300000);
return;
}
count++;
// specify user data to change:
var set = {};
var inc = {};
if (user.migration !== migrationName) {
if (user.items.mounts['JackOLantern-Base']) {
set = {'migration':migrationName, 'items.pets.JackOLantern-Ghost':5};
} else if (user.items.pets['JackOLantern-Base']) {
set = {'migration':migrationName, 'items.mounts.JackOLantern-Base':true};
} else {
set = {'migration':migrationName, 'items.pets.JackOLantern-Base':5};
}
inc = {
'items.food.Candy_Base': 1,
'items.food.Candy_CottonCandyBlue': 1,
'items.food.Candy_CottonCandyPink': 1,
'items.food.Candy_Desert': 1,
'items.food.Candy_Golden': 1,
'items.food.Candy_Red': 1,
'items.food.Candy_Shade': 1,
'items.food.Candy_Skeleton': 1,
'items.food.Candy_White': 1,
'items.food.Candy_Zombie': 1,
}
}
dbUsers.update({_id:user._id}, {$set:set, $inc:inc});
if (count%progressCount == 0) console.warn(count + ' ' + user._id);
if (user._id == authorUuid) console.warn(authorName + ' processed');
});
function displayData() {
console.warn('\n' + count + ' users processed\n');
return exiting(0);
}
function exiting(code, msg) {
code = code || 0; // 0 = success
if (code && !msg) { msg = 'ERROR!'; }
if (msg) {
if (code) { console.error(msg); }
else { console.log( msg); }
}
process.exit(code);
}
+75
View File
@@ -0,0 +1,75 @@
var migrationName = '20161102_takeThis.js';
var authorName = 'Sabe'; // in case script author needs to know when their ...
var authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; //... own data is done
/*
* Award Take This ladder items to participants in this month's challenge
*/
var mongo = require('mongoskin');
var connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
var dbUsers = mongo.db(connectionString).collection('users');
// specify a query to limit the affected users (empty for all users):
var query = {
'migration':{$ne:migrationName},
'challenges':{$in:['d1be0965-e909-4d30-82fa-9a0011f885b2']}
};
// specify fields we are interested in to limit retrieved data (empty if we're not reading data):
var fields = {
'items.gear.owned': 1
};
console.warn('Updating users...');
var progressCount = 1000;
var count = 0;
dbUsers.findEach(query, fields, {batchSize:250}, function(err, user) {
if (err) { return exiting(1, 'ERROR! ' + err); }
if (!user) {
console.warn('All appropriate users found and modified.');
setTimeout(displayData, 300000);
return;
}
count++;
// specify user data to change:
var set = {};
if (typeof user.items.gear.owned.head_special_takeThis !== 'undefined') {
set = {'migration':migrationName, 'items.gear.owned.body_special_takeThis':false};
} else if (typeof user.items.gear.owned.armor_special_takeThis !== 'undefined') {
set = {'migration':migrationName, 'items.gear.owned.head_special_takeThis':false};
} else if (typeof user.items.gear.owned.weapon_special_takeThis !== 'undefined') {
set = {'migration':migrationName, 'items.gear.owned.armor_special_takeThis':false};
} else if (typeof user.items.gear.owned.shield_special_takeThis !== 'undefined') {
set = {'migration':migrationName, 'items.gear.owned.weapon_special_takeThis':false};
} else {
set = {'migration':migrationName, 'items.gear.owned.shield_special_takeThis':false};
}
dbUsers.update({_id:user._id}, {$set:set});
if (count%progressCount == 0) console.warn(count + ' ' + user._id);
if (user._id == authorUuid) console.warn(authorName + ' processed');
});
function displayData() {
console.warn('\n' + count + ' users processed\n');
return exiting(0);
}
function exiting(code, msg) {
code = code || 0; // 0 = success
if (code && !msg) { msg = 'ERROR!'; }
if (msg) {
if (code) { console.error(msg); }
else { console.log( msg); }
}
process.exit(code);
}
+74
View File
@@ -0,0 +1,74 @@
var migrationName = '20161122_turkey_ladder.js';
var authorName = 'Sabe'; // in case script author needs to know when their ...
var authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; //... own data is done
/*
* Yearly Turkey Day award. Turkey pet, Turkey mount, Gilded Turkey pet, Gilded Turkey mount
*/
var mongo = require('mongoskin');
var connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
var dbUsers = mongo.db(connectionString).collection('users');
// specify a query to limit the affected users (empty for all users):
var query = {
'migration':{$ne:migrationName},
'auth.timestamps.loggedin':{$gt:new Date('2016-10-31')} // Extend timeframe each run of migration
};
// specify fields we are interested in to limit retrieved data (empty if we're not reading data):
var fields = {
'migration': 1,
'items.mounts': 1,
'items.pets': 1,
};
console.warn('Updating users...');
var progressCount = 1000;
var count = 0;
dbUsers.findEach(query, fields, {batchSize:250}, function(err, user) {
if (err) { return exiting(1, 'ERROR! ' + err); }
if (!user) {
console.warn('All appropriate users found and modified.');
setTimeout(displayData, 300000);
return;
}
count++;
// specify user data to change:
var set = {};
if (user.items.pets['Turkey-Gilded']) {
set = {'migration':migrationName, 'items.mounts.Turkey-Gilded':true};
} else if (user.items.mounts['Turkey-Base']) {
set = {'migration':migrationName, 'items.pets.Turkey-Gilded':5};
} else if (user.items.pets['Turkey-Base']) {
set = {'migration':migrationName, 'items.mounts.Turkey-Base':true};
} else {
set = {'migration':migrationName, 'items.pets.Turkey-Base':5};
}
dbUsers.update({_id:user._id}, {$set:set});
if (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);
}
+73
View File
@@ -0,0 +1,73 @@
var migrationName = '20161230_nye_hats.js';
var authorName = 'Sabe'; // in case script author needs to know when their ...
var authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; //... own data is done
/*
* Yearly New Year's party hat award
*/
var mongo = require('mongoskin');
var connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
var dbUsers = mongo.db(connectionString).collection('users');
// specify a query to limit the affected users (empty for all users):
var query = {
'migration':{$ne:migrationName},
'auth.timestamps.loggedin':{$gt:new Date('2016-11-30')} // Remove after first run
};
// specify fields we are interested in to limit retrieved data (empty if we're not reading data):
var fields = {
'items.gear.owned': 1,
};
console.warn('Updating users...');
var progressCount = 1000;
var count = 0;
dbUsers.findEach(query, fields, {batchSize:250}, function(err, user) {
if (err) { return exiting(1, 'ERROR! ' + err); }
if (!user) {
console.warn('All appropriate users found and modified.');
setTimeout(displayData, 300000);
return;
}
count++;
// specify user data to change:
var set = {};
if (typeof user.items.gear.owned.head_special_nye2015 !== 'undefined') {
set = {'migration':migrationName, 'items.gear.owned.head_special_nye2016':false};
} else if (typeof user.items.gear.owned.head_special_nye2014 !== 'undefined') {
set = {'migration':migrationName, 'items.gear.owned.head_special_nye2015':false};
} else if (typeof user.items.gear.owned.head_special_nye !== 'undefined') {
set = {'migration':migrationName, 'items.gear.owned.head_special_nye2014':false};
} else {
set = {'migration':migrationName, 'items.gear.owned.head_special_nye':false};
}
dbUsers.update({_id:user._id}, {$set:set});
if (count%progressCount == 0) console.warn(count + ' ' + user._id);
if (user._id == authorUuid) console.warn(authorName + ' processed');
});
function displayData() {
console.warn('\n' + count + ' users processed\n');
return exiting(0);
}
function exiting(code, msg) {
code = code || 0; // 0 = success
if (code && !msg) { msg = 'ERROR!'; }
if (msg) {
if (code) { console.error(msg); }
else { console.log( msg); }
}
process.exit(code);
}
+113
View File
@@ -0,0 +1,113 @@
var migrationName = '20170120_missing_incentive.js';
var authorName = 'Sabe'; // in case script author needs to know when their ...
var authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; //... own data is done
/*
* Award missing Royal Purple Hatching Potion to users with 55+ check-ins
* Reduce users with impossible check-in counts to a reasonable number
*/
import monk from 'monk';
import common from '../website/common';
var connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
var dbUsers = monk(connectionString).get('users', { castIds: false });
function processUsers(lastId) {
// specify a query to limit the affected users (empty for all users):
var query = {
'loginIncentives': {$gt:54},
'migration': {$ne: migrationName},
};
if (lastId) {
query._id = {
$gt: lastId
}
}
dbUsers.find(query, {
sort: {_id: 1},
limit: 250,
fields: [] // specify fields we are interested in to limit retrieved data (empty if we're not reading data):
})
.then(updateUsers)
.catch(function (err) {
console.log(err);
return exiting(1, 'ERROR! ' + err);
});
}
var progressCount = 1000;
var count = 0;
function updateUsers (users) {
if (!users || users.length === 0) {
console.warn('All appropriate users found and modified.');
displayData();
return;
}
var userPromises = users.map(updateUser);
var lastUser = users[users.length - 1];
return Promise.all(userPromises)
.then(function () {
processUsers(lastUser._id);
});
}
function updateUser (user) {
count++;
var language = user.preferences.language || 'en';
var set = {'migration': migrationName};
var inc = {'items.hatchingPotions.RoyalPurple': 1};
if (user.loginIncentives > 58) {
set = {'migration': migrationName, 'loginIncentives': 58};
}
var push = {
'notifications': {
'type': 'LOGIN_INCENTIVE',
'data': {
'nextRewardAt': 60,
'rewardKey': [
'Pet_HatchingPotion_Purple',
],
'rewardText': common.i18n.t('potion', {potionType: common.i18n.t('hatchingPotionRoyalPurple', language)}, language),
'reward': [
{
'premium': true,
'key': 'RoyalPurple',
'limited': true,
'value': 2,
}
],
'message': common.i18n.t('unlockedCheckInReward', language),
},
'id': common.uuid(),
}
};
dbUsers.update({_id: user._id}, {$set:set, $push:push, $inc:inc});
if (count % progressCount == 0) console.warn(count + ' ' + user._id);
if (user._id == authorUuid) console.warn(authorName + ' processed');
}
function displayData() {
console.warn('\n' + count + ' users processed\n');
return exiting(0);
}
function exiting(code, msg) {
code = code || 0; // 0 = success
if (code && !msg) { msg = 'ERROR!'; }
if (msg) {
if (code) { console.error(msg); }
else { console.log( msg); }
}
process.exit(code);
}
module.exports = processUsers;
+109
View File
@@ -0,0 +1,109 @@
var migrationName = '20170131_habit_birthday.js';
var authorName = 'Sabe'; // in case script author needs to know when their ...
var authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; //... own data is done
/*
* Award 2017 party robes if user has 2016 robes, 2016 robes if they have the 2015 robes,
* 2015 robes if they have the 2014 robes, and 2014 robes otherwise. Also cake!
*/
var monk = require('monk');
var connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
var dbUsers = monk(connectionString).get('users', { castIds: false });
function processUsers(lastId) {
// specify a query to limit the affected users (empty for all users):
var query = {
'migration':{$ne:migrationName},
'auth.timestamps.loggedin':{$gt:new Date('2017-01-24')}, // remove after first run to cover remaining users
};
if (lastId) {
query._id = {
$gt: lastId
}
}
dbUsers.find(query, {
sort: {_id: 1},
limit: 250,
fields: [ // specify fields we are interested in to limit retrieved data (empty if we're not reading data)
'items.gear.owned'
],
})
.then(updateUsers)
.catch(function (err) {
console.log(err);
return exiting(1, 'ERROR! ' + err);
});
}
var progressCount = 1000;
var count = 0;
function updateUsers (users) {
if (!users || users.length === 0) {
console.warn('All appropriate users found and modified.');
displayData();
return;
}
var userPromises = users.map(updateUser);
var lastUser = users[users.length - 1];
return Promise.all(userPromises)
.then(function () {
processUsers(lastUser._id);
});
}
function updateUser (user) {
count++;
var set = {'migration':migrationName};
if (user.items && user.items.gear && user.items.gear.owned && user.items.gear.owned.hasOwnProperty('armor_special_birthday2016')) {
set['items.gear.owned.armor_special_birthday2017'] = false;
} else if (user.items && user.items.gear && user.items.gear.owned && user.items.gear.owned.hasOwnProperty('armor_special_birthday2015')) {
set['items.gear.owned.armor_special_birthday2016'] = false;
} else if (user.items && user.items.gear && user.items.gear.owned && user.items.gear.owned.hasOwnProperty('armor_special_birthday')) {
set['items.gear.owned.armor_special_birthday2015'] = false;
} else {
set['items.gear.owned.armor_special_birthday'] = false;
}
var inc = {
'items.food.Cake_Skeleton':1,
'items.food.Cake_Base':1,
'items.food.Cake_CottonCandyBlue':1,
'items.food.Cake_CottonCandyPink':1,
'items.food.Cake_Shade':1,
'items.food.Cake_White':1,
'items.food.Cake_Golden':1,
'items.food.Cake_Zombie':1,
'items.food.Cake_Desert':1,
'items.food.Cake_Red':1,
'achievements.habitBirthdays':1
};
dbUsers.update({_id: user._id}, {$set:set, $inc:inc});
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;
+21
View File
@@ -0,0 +1,21 @@
require("babel-register");
require("babel-polyfill");
// This file must use ES5, everything required can be in ES6
function setUpServer () {
var nconf = require('nconf');
var mongoose = require('mongoose');
var Bluebird = require('bluebird');
var setupNconf = require('../website/server/libs/setupNconf');
setupNconf();
// We require src/server and npt src/index because
// 1. nconf is already setup
// 2. we don't need clustering
require('../website/server/server'); // eslint-disable-line global-require
}
setUpServer();
// Replace this with your migration
var processUsers = require('./new_stuff');
processUsers();
+1 -1
View File
@@ -2,7 +2,7 @@ var _id = '';
var update = {
$addToSet: {
'purchased.plan.mysteryItems':{
$each:['head_mystery_201609','armor_mystery_201609']
$each:['head_mystery_201702','back_mystery_201702']
}
}
};
+44 -22
View File
@@ -6,49 +6,69 @@ var authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; //... own data is done
* set the newStuff flag in all user accounts so they see a Bailey message
*/
var mongo = require('mongoskin');
var monk = require('monk');
var connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
var dbUsers = monk(connectionString).get('users', { castIds: false });
var dbUsers = mongo.db(connectionString).collection('users');
function processUsers(lastId) {
// specify a query to limit the affected users (empty for all users):
var query = {
'flags.newStuff': {$ne:true},
};
// specify a query to limit the affected users (empty for all users):
var query = {
'flags.newStuff':{$ne:true}
};
if (lastId) {
query._id = {
$gt: lastId
}
}
// specify fields we are interested in to limit retrieved data (empty if we're not reading data):
var fields = {
};
dbUsers.find(query, {
sort: {_id: 1},
limit: 250,
fields: [] // specify fields we are interested in to limit retrieved data (empty if we're not reading data):
})
.then(updateUsers)
.catch(function (err) {
console.log(err);
return exiting(1, 'ERROR! ' + err);
});
}
console.warn('Updating users...');
var progressCount = 1000;
var count = 0;
dbUsers.findEach(query, fields, {batchSize:250}, function(err, user) {
if (err) { return exiting(1, 'ERROR! ' + err); }
if (!user) {
function updateUsers (users) {
if (!users || users.length === 0) {
console.warn('All appropriate users found and modified.');
setTimeout(displayData, 300000);
displayData();
return;
}
var userPromises = users.map(updateUser);
var lastUser = users[users.length - 1];
return Promise.all(userPromises)
.then(function () {
processUsers(lastUser._id);
});
}
function updateUser (user) {
count++;
// specify user data to change:
var set = {'flags.newStuff':true};
var set = {'flags.newStuff': true};
dbUsers.update({_id:user._id}, {$set:set});
dbUsers.update({_id: user._id}, {$set:set});
if (count%progressCount == 0) console.warn(count + ' ' + user._id);
if (count % progressCount == 0) console.warn(count + ' ' + user._id);
if (user._id == authorUuid) console.warn(authorName + ' processed');
});
}
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!'; }
@@ -58,3 +78,5 @@ function exiting(code, msg) {
}
process.exit(code);
}
module.exports = processUsers;
+46 -27
View File
@@ -6,52 +6,69 @@ var authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; //... own data is done
* Remove flag stating that the Enchanted Armoire is empty, for when new equipment is added
*/
var dbserver = 'localhost:27017'; // FOR TEST DATABASE
// var dbserver = 'username:password@ds031379-a0.mongolab.com:31379'; // FOR PRODUCTION DATABASE
var dbname = 'habitrpg';
var monk = require('monk');
var connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
var dbUsers = monk(connectionString).get('users', { castIds: false });
var mongo = require('mongoskin');
var _ = require('lodash');
function processUsers(lastId) {
// specify a query to limit the affected users (empty for all users):
var query = {
'flags.armoireEmpty': true,
};
var dbUsers = mongo.db(dbserver + '/' + dbname + '?auto_reconnect').collection('users');
if (lastId) {
query._id = {
$gt: lastId
}
}
// specify a query to limit the affected users (empty for all users):
var query = {
'flags.armoireEmpty':true
};
dbUsers.find(query, {
sort: {_id: 1},
limit: 250,
fields: [] // specify fields we are interested in to limit retrieved data (empty if we're not reading data):
})
.then(updateUsers)
.catch(function (err) {
console.log(err);
return exiting(1, 'ERROR! ' + err);
});
}
// specify fields we are interested in to limit retrieved data (empty if we're not reading data):
var fields = {
'flags.armoireEmpty':1
};
console.warn('Updating users...');
var progressCount = 1000;
var count = 0;
dbUsers.findEach(query, fields, {batchSize:250}, function(err, user) {
if (err) { return exiting(1, 'ERROR! ' + err); }
if (!user) {
function updateUsers (users) {
if (!users || users.length === 0) {
console.warn('All appropriate users found and modified.');
return displayData();
displayData();
return;
}
var userPromises = users.map(updateUser);
var lastUser = users[users.length - 1];
return Promise.all(userPromises)
.then(function () {
processUsers(lastUser._id);
});
}
function updateUser (user) {
count++;
// specify user data to change:
var set = {'migration':migrationName, 'flags.armoireEmpty':false};
var set = {'migration': migrationName, 'flags.armoireEmpty': false};
dbUsers.update({_id:user._id}, {$set:set});
dbUsers.update({_id: user._id}, {$set:set});
if (count%progressCount == 0) console.warn(count + ' ' + user._id);
if (count % progressCount == 0) console.warn(count + ' ' + user._id);
if (user._id == authorUuid) console.warn(authorName + ' processed');
});
}
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!'; }
@@ -61,3 +78,5 @@ function exiting(code, msg) {
}
process.exit(code);
}
module.exports = processUsers;
+115
View File
@@ -0,0 +1,115 @@
var migrationName = 'restore_profile_data.js';
var authorName = 'ThehollidayInn'; // in case script author needs to know when their ...
var authorUuid = ''; //... own data is done
/*
* Check if users have empty profile data in new database and update it with old database info
*/
var monk = require('monk');
var connectionString = ''; // FOR TEST DATABASE
var dbUsers = monk(connectionString).get('users', { castIds: false });
var monk2 = require('monk');
var oldDbConnectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
var olDbUsers = monk2(oldDbConnectionString).get('users', { castIds: false });
function processUsers(lastId)
{
// specify a query to limit the affected users (empty for all users):
var query = {
// 'profile.name': 'profile name not found',
'profile.blurb': null,
// 'auth.timestamps.loggedin': {$gt: new Date('11/30/2016')},
};
if (lastId) {
query._id = {
$gt: lastId
}
}
dbUsers.find(query, {
sort: {_id: 1},
limit: 250,
fields: ['_id', 'profile', 'auth.timestamps.loggedin'] // specify fields we are interested in to limit retrieved data (empty if we're not reading data):
})
.then(updateUsers)
.catch(function (err) {
console.log(err);
return exiting(1, 'ERROR! ' + err);
});
}
var progressCount = 1000;
var count = 0;
function updateUsers (users) {
if (!users || users.length === 0) {
console.warn('All appropriate users found and modified.');
setTimeout(displayData, 300000);
return;
}
var userPaymentPromises = users.map(updateUser);
var lastUser = users[users.length - 1];
return Promise.all(userPaymentPromises)
.then(function () {
processUsers(lastUser._id);
});
}
function updateUser (user) {
count++;
if (!user.profile.name || user.profile.name === 'profile name not found' || !user.profile.imageUrl || !user.profile.blurb) {
return olDbUsers.findOne({_id: user._id}, '_id profile')
.then((oldUserData) => {
if (!oldUserData) return;
// specify user data to change:
var set = {};
if (oldUserData.profile.name === 'profile name not found') return;
var userNeedsProfileName = !user.profile.name || user.profile.name === 'profile name not found';
if (userNeedsProfileName && oldUserData.profile.name) {
set['profile.name'] = oldUserData.profile.name;
}
if (!user.profile.imageUrl && oldUserData.profile.imageUrl) {
set['profile.imageUrl'] = oldUserData.profile.imageUrl;
}
if (!user.profile.blurb && oldUserData.profile.blurb) {
set['profile.blurb'] = oldUserData.profile.blurb;
}
if (Object.keys(set).length !== 0 && set.constructor === Object) {
console.log(set)
return dbUsers.update({_id: user._id}, {$set:set});
}
});
}
if (count % progressCount == 0) console.warn(count + ' ' + user._id);
if (user._id == authorUuid) console.warn(authorName + ' processed');
}
function displayData() {
console.warn('\n' + count + ' users processed\n');
return exiting(0);
}
function exiting(code, msg) {
code = code || 0; // 0 = success
if (code && !msg) { msg = 'ERROR!'; }
if (msg) {
if (code) { console.error(msg); }
else { console.log( msg); }
}
process.exit(code);
}
processUsers()
+101
View File
@@ -0,0 +1,101 @@
var migrationName = '20170201_takeThis.js'; // Update per month
var authorName = 'Sabe'; // in case script author needs to know when their ...
var authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; //... own data is done
/*
* Award Take This ladder items to participants in this month's challenge
*/
var monk = require('monk');
var connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
var dbUsers = monk(connectionString).get('users', { castIds: false });
function processUsers(lastId) {
// specify a query to limit the affected users (empty for all users):
var query = {
'migration':{$ne:migrationName},
'challenges':{$in:['b1d436b5-c784-42e3-9b07-7072479a6f8e']} // Update per month
};
if (lastId) {
query._id = {
$gt: lastId
}
}
dbUsers.find(query, {
sort: {_id: 1},
limit: 250,
fields: [
'items.gear.owned',
] // specify fields we are interested in to limit retrieved data (empty if we're not reading data):
})
.then(updateUsers)
.catch(function (err) {
console.log(err);
return exiting(1, 'ERROR! ' + err);
});
}
var progressCount = 1000;
var count = 0;
function updateUsers (users) {
if (!users || users.length === 0) {
console.warn('All appropriate users found and modified.');
displayData();
return;
}
var userPromises = users.map(updateUser);
var lastUser = users[users.length - 1];
return Promise.all(userPromises)
.then(function () {
processUsers(lastUser._id);
});
}
function updateUser (user) {
count++;
var set = {};
if (typeof user.items.gear.owned.back_special_takeThis !== 'undefined') {
set = {'migration':migrationName};
} else if (typeof user.items.gear.owned.body_special_takeThis !== 'undefined') {
set = {'migration':migrationName, 'items.gear.owned.back_special_takeThis':false};
} else if (typeof user.items.gear.owned.head_special_takeThis !== 'undefined') {
set = {'migration':migrationName, 'items.gear.owned.body_special_takeThis':false};
} else if (typeof user.items.gear.owned.armor_special_takeThis !== 'undefined') {
set = {'migration':migrationName, 'items.gear.owned.head_special_takeThis':false};
} else if (typeof user.items.gear.owned.weapon_special_takeThis !== 'undefined') {
set = {'migration':migrationName, 'items.gear.owned.armor_special_takeThis':false};
} else if (typeof user.items.gear.owned.shield_special_takeThis !== 'undefined') {
set = {'migration':migrationName, 'items.gear.owned.weapon_special_takeThis':false};
} else {
set = {'migration':migrationName, 'items.gear.owned.shield_special_takeThis':false};
}
dbUsers.update({_id: user._id}, {$set:set});
if (count % progressCount == 0) console.warn(count + ' ' + user._id);
if (user._id == authorUuid) console.warn(authorName + ' processed');
}
function displayData() {
console.warn('\n' + count + ' users processed\n');
return exiting(0);
}
function exiting(code, msg) {
code = code || 0; // 0 = success
if (code && !msg) { msg = 'ERROR!'; }
if (msg) {
if (code) { console.error(msg); }
else { console.log( msg); }
}
process.exit(code);
}
module.exports = processUsers;
+4880 -1368
View File
File diff suppressed because it is too large Load Diff
+51 -45
View File
@@ -1,27 +1,31 @@
{
"name": "habitica",
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
"version": "3.50.0",
"version": "3.78.0",
"main": "./website/server/index.js",
"dependencies": {
"@slack/client": "3.6.0",
"@slack/client": "^3.8.1",
"accepts": "^1.3.2",
"amazon-payments": "0.0.4",
"amplitude": "^2.0.3",
"apidoc": "^0.16.0",
"apidoc": "^0.17.5",
"apn": "^1.7.6",
"async": "^1.5.0",
"autoprefixer": "^6.4.0",
"aws-sdk": "^2.0.25",
"axios": "^0.15.3",
"babel-core": "^6.0.0",
"babel-loader": "^6.0.0",
"babel-plugin-syntax-async-functions": "^6.13.0",
"babel-plugin-transform-async-to-module-method": "^6.8.0",
"babel-plugin-transform-object-rest-spread": "^6.16.0",
"babel-plugin-transform-regenerator": "^6.16.1",
"babel-polyfill": "^6.6.1",
"babel-preset-es2015": "^6.6.0",
"babel-register": "^6.6.0",
"babel-runtime": "^6.11.6",
"babelify": "^7.2.0",
"bcrypt": "^1.0.2",
"bluebird": "^3.3.5",
"body-parser": "^1.15.0",
"bower": "~1.3.12",
@@ -29,8 +33,8 @@
"compression": "^1.6.1",
"connect-ratelimit": "0.0.7",
"cookie-session": "^1.2.0",
"coupon-code": "^0.4.3",
"css-loader": "^0.23.1",
"coupon-code": "^0.4.5",
"css-loader": "^0.26.1",
"csv-stringify": "^1.0.2",
"cwait": "^1.0.0",
"domain-middleware": "~0.1.0",
@@ -38,8 +42,8 @@
"express": "~4.14.0",
"express-csv": "~0.6.0",
"express-validator": "^2.18.0",
"extract-text-webpack-plugin": "^1.0.1",
"file-loader": "^0.8.4",
"extract-text-webpack-plugin": "^2.0.0-rc.3",
"file-loader": "^0.10.0",
"glob": "^4.3.5",
"got": "^6.1.1",
"grunt": "~0.4.1",
@@ -64,35 +68,35 @@
"image-size": "~0.3.2",
"in-app-purchase": "^1.1.6",
"jade": "~1.11.0",
"jquery": "^3.1.1",
"js2xmlparser": "~1.0.0",
"json-loader": "^0.5.4",
"less": "^2.7.1",
"less-loader": "^2.2.3",
"lodash": "^3.10.1",
"lodash.setwith": "^4.2.0",
"lodash.pickby": "^4.2.0",
"lodash.setwith": "^4.2.0",
"merge-stream": "^1.0.0",
"method-override": "^2.3.5",
"moment": "^2.13.0",
"mongoose": "^4.4.16",
"mongoose": "^4.7.1",
"mongoose-id-autoinc": "~2013.7.14-4",
"morgan": "^1.7.0",
"nconf": "~0.8.2",
"newrelic": "^1.27.2",
"nib": "^1.1.0",
"node-gcm": "^0.14.4",
"nodemailer": "^2.3.2",
"object-path": "^0.9.2",
"ora": "^0.2.0",
"ora": "^1.1.0",
"pageres": "^4.1.1",
"passport": "~0.2.1",
"passport-facebook": "2.0.0",
"passport": "^0.3.2",
"passport-facebook": "^2.0.0",
"passport-google-oauth20": "1.0.0",
"paypal-ipn": "3.0.0",
"paypal-rest-sdk": "^1.2.1",
"postcss-easy-import": "^2.0.0",
"pretty-data": "^0.40.0",
"ps-tree": "^1.0.0",
"pug": "^2.0.0-beta6",
"pug": "^2.0.0-beta11",
"push-notify": "habitrpg/push-notify#v1.2.0",
"pusher": "^1.3.0",
"request": "~2.74.0",
@@ -101,30 +105,30 @@
"s3-upload-stream": "^1.0.6",
"semantic-ui-less": "~2.2.4",
"serve-favicon": "^2.3.0",
"shelljs": "^0.6.0",
"shelljs": "^0.7.6",
"stripe": "^4.2.0",
"superagent": "^1.8.3",
"superagent": "^3.4.3",
"universal-analytics": "~0.3.2",
"url-loader": "^0.5.7",
"useragent": "2.1.9",
"uuid": "^2.0.1",
"useragent": "^2.1.9",
"uuid": "^3.0.1",
"validator": "^4.9.0",
"vinyl-buffer": "^1.0.0",
"vinyl-source-stream": "^1.1.0",
"vue": "^2.0.0-rc.6",
"vue-hot-reload-api": "^1.2.0",
"vue-loader": "^9.4.0",
"vue-resource": "^1.0.2",
"vue": "^2.1.0",
"vue-loader": "^11.0.0",
"vue-router": "^2.0.0-rc.5",
"webpack": "^1.12.2",
"webpack-merge": "^0.8.3",
"vue-style-loader": "^2.0.0",
"vue-template-compiler": "^2.1.10",
"webpack": "^2.2.1",
"webpack-merge": "^2.6.1",
"winston": "^2.1.0",
"xml2js": "^0.4.4"
},
"private": true,
"engines": {
"node": "^4.3.1",
"npm": "^3.8.9"
"node": "^6.9.1",
"npm": "^4.0.2"
},
"scripts": {
"lint": "eslint --ext .js,.vue .",
@@ -147,38 +151,37 @@
"sprites": "gulp sprites:compile",
"client:dev": "node webpack/dev-server.js",
"client:build": "node webpack/build.js",
"client:unit": "karma start test/client/unit/karma.conf.js --single-run",
"client:unit:watch": "karma start test/client/unit/karma.conf.js",
"client:unit": "cross-env NODE_ENV=test karma start test/client/unit/karma.conf.js --single-run",
"client:unit:watch": "cross-env NODE_ENV=test karma start test/client/unit/karma.conf.js",
"client:e2e": "node test/client/e2e/runner.js",
"client:test": "npm run client:unit && npm run client:e2e",
"start": "gulp run:dev",
"postinstall": "bower --config.interactive=false install -f; gulp build; npm run client:build"
},
"devDependencies": {
"babel-eslint": "^6.0.0",
"babel-plugin-istanbul": "^4.0.0",
"chai": "^3.4.0",
"chai-as-promised": "^5.1.0",
"chalk": "^1.1.3",
"chromedriver": "^2.21.2",
"chromedriver": "^2.27.2",
"connect-history-api-fallback": "^1.1.0",
"coveralls": "^2.11.2",
"cross-spawn": "^2.1.5",
"cross-env": "^3.1.4",
"cross-spawn": "^5.0.1",
"csv": "~0.3.6",
"deep-diff": "~0.1.4",
"eslint": "~2.12.0",
"eslint-config-habitrpg": "^1.0.0",
"eslint": "^3.0.0",
"eslint-config-habitrpg": "^2.0.0",
"eslint-friendly-formatter": "^2.0.5",
"eslint-loader": "^1.3.0",
"eslint-plugin-babel": "^3.0.0",
"eslint-plugin-html": "^1.3.0",
"eslint-plugin-mocha": "^2.1.0",
"eslint-plugin-html": "^2.0.0",
"eslint-plugin-mocha": "^4.7.0",
"event-stream": "^3.2.2",
"eventsource-polyfill": "^0.9.6",
"expect.js": "~0.2.0",
"grunt-karma": "~0.12.1",
"http-proxy-middleware": "^0.12.0",
"inject-loader": "^2.0.1",
"isparta-loader": "^2.0.0",
"http-proxy-middleware": "^0.17.0",
"inject-loader": "^3.0.0-beta4",
"istanbul": "^0.3.14",
"karma": "^1.3.0",
"karma-babel-preprocessor": "^6.0.1",
@@ -190,23 +193,26 @@
"karma-sinon-chai": "^1.2.0",
"karma-sourcemap-loader": "^0.3.7",
"karma-spec-reporter": "0.0.24",
"karma-webpack": "^1.7.0",
"karma-webpack": "^2.0.2",
"lcov-result-merger": "^1.0.2",
"lolex": "^1.4.0",
"mocha": "^2.3.3",
"mongodb": "^2.0.46",
"mongoskin": "~2.1.0",
"nightwatch": "^0.8.18",
"monk": "^4.0.0",
"nightwatch": "^0.9.12",
"phantomjs-prebuilt": "^2.1.12",
"protractor": "^3.1.1",
"require-again": "^2.0.0",
"rewire": "^2.3.3",
"selenium-server": "2.53.0",
"selenium-server": "^3.0.1",
"sinon": "^1.17.2",
"sinon-chai": "^2.8.0",
"sinon-stub-promise": "^4.0.0",
"superagent-defaults": "^0.1.13",
"vinyl-transform": "^1.0.0",
"webpack-dev-middleware": "^1.4.0",
"webpack-hot-middleware": "^2.6.0"
"webpack-bundle-analyzer": "^2.2.1",
"webpack-dev-middleware": "^1.10.0",
"webpack-hot-middleware": "^2.6.1"
}
}
+1 -1
View File
@@ -1,7 +1,7 @@
{
"extends": [
"habitrpg/mocha",
"habitrpg/babel"
"habitrpg/esnext"
],
"env": {
"node": true,
@@ -5,7 +5,7 @@ import {
translate as t,
} from '../../../../helpers/api-v3-integration.helper';
describe('GET challenges/group/:groupId', () => {
describe('GET challenges/groups/:groupId', () => {
context('Public Guild', () => {
let publicGuild, user, nonMember, challenge, challenge2;
@@ -35,6 +35,24 @@ describe('POST /chat', () => {
});
});
it('Returns an error when an empty message is provided', async () => {
await expect(user.post(`/groups/${groupWithChat._id}/chat`, { message: ' '}))
.to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('invalidReqParams'),
});
});
it('Returns an error when an message containing only newlines is provided', async () => {
await expect(user.post(`/groups/${groupWithChat._id}/chat`, { message: '\n\n'}))
.to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('invalidReqParams'),
});
});
it('Returns an error when group is not found', async () => {
await expect(user.post('/groups/invalidID/chat', { message: testMessage})).to.eventually.be.rejected.and.eql({
code: 404,
@@ -7,15 +7,21 @@ import {
import moment from 'moment';
describe('GET /export/history.csv', () => {
it('should return a valid CSV file with tasks history data', async () => {
// TODO disabled because it randomly causes the build to fail
xit('should return a valid CSV file with tasks history data', async () => {
let user = await generateUser();
let tasks = await user.post('/tasks/user', [
{type: 'habit', text: 'habit 1'},
{type: 'daily', text: 'daily 1'},
{type: 'habit', text: 'habit 1'},
{type: 'habit', text: 'habit 2'},
{type: 'todo', text: 'todo 1'},
]);
// to handle occasional inconsistency in task creation order
tasks.sort(function (a, b) {
return a.text.localeCompare(b.text);
});
// score all the tasks twice
await user.post(`/tasks/${tasks[0]._id}/score/up`);
await user.post(`/tasks/${tasks[1]._id}/score/up`);
@@ -28,7 +34,7 @@ describe('GET /export/history.csv', () => {
await user.post(`/tasks/${tasks[3]._id}/score/up`);
// adding an history entry to daily 1 manually because cron didn't run yet
await updateDocument('tasks', tasks[1], {
await updateDocument('tasks', tasks[0], {
history: [{value: 3.2, date: Number(new Date())}],
});
@@ -41,11 +47,11 @@ describe('GET /export/history.csv', () => {
let splitRes = res.split('\n');
expect(splitRes[0]).to.equal('Task Name,Task ID,Task Type,Date,Value');
expect(splitRes[1]).to.equal(`habit 1,${tasks[0]._id},habit,${moment(tasks[0].history[0].date).format('YYYY-MM-DD HH:mm:ss')},${tasks[0].history[0].value}`);
expect(splitRes[2]).to.equal(`habit 1,${tasks[0]._id},habit,${moment(tasks[0].history[1].date).format('YYYY-MM-DD HH:mm:ss')},${tasks[0].history[1].value}`);
expect(splitRes[3]).to.equal(`habit 2,${tasks[2]._id},habit,${moment(tasks[2].history[0].date).format('YYYY-MM-DD HH:mm:ss')},${tasks[2].history[0].value}`);
expect(splitRes[4]).to.equal(`habit 2,${tasks[2]._id},habit,${moment(tasks[2].history[1].date).format('YYYY-MM-DD HH:mm:ss')},${tasks[2].history[1].value}`);
expect(splitRes[5]).to.equal(`daily 1,${tasks[1]._id},daily,${moment(tasks[1].history[0].date).format('YYYY-MM-DD HH:mm:ss')},${tasks[1].history[0].value}`);
expect(splitRes[1]).to.equal(`daily 1,${tasks[0]._id},daily,${moment(tasks[0].history[0].date).format('YYYY-MM-DD HH:mm:ss')},${tasks[0].history[0].value}`);
expect(splitRes[2]).to.equal(`habit 1,${tasks[1]._id},habit,${moment(tasks[1].history[0].date).format('YYYY-MM-DD HH:mm:ss')},${tasks[1].history[0].value}`);
expect(splitRes[3]).to.equal(`habit 1,${tasks[1]._id},habit,${moment(tasks[1].history[1].date).format('YYYY-MM-DD HH:mm:ss')},${tasks[1].history[1].value}`);
expect(splitRes[4]).to.equal(`habit 2,${tasks[2]._id},habit,${moment(tasks[2].history[0].date).format('YYYY-MM-DD HH:mm:ss')},${tasks[2].history[0].value}`);
expect(splitRes[5]).to.equal(`habit 2,${tasks[2]._id},habit,${moment(tasks[2].history[1].date).format('YYYY-MM-DD HH:mm:ss')},${tasks[2].history[1].value}`);
expect(splitRes[6]).to.equal('');
});
});
@@ -7,7 +7,8 @@ import Bluebird from 'bluebird';
let parseStringAsync = Bluebird.promisify(xml2js.parseString, {context: xml2js});
describe('GET /export/userdata.xml', () => {
it('should return a valid XML file with user data', async () => {
// TODO disabled because it randomly causes the build to fail
xit('should return a valid XML file with user data', async () => {
let user = await generateUser();
let tasks = await user.post('/tasks/user', [
{type: 'habit', text: 'habit 1'},
@@ -82,8 +82,10 @@ describe('GET /groups/:groupId/members', () => {
'backer', 'contributor', 'auth', 'items', 'inbox',
]);
expect(Object.keys(memberRes.auth)).to.eql(['timestamps']);
expect(Object.keys(memberRes.preferences).sort()).to.eql(['size', 'hair', 'skin', 'shirt',
'chair', 'costume', 'sleep', 'background'].sort());
expect(Object.keys(memberRes.preferences).sort()).to.eql([
'size', 'hair', 'skin', 'shirt',
'chair', 'costume', 'sleep', 'background',
].sort());
expect(memberRes.stats.maxMP).to.exist;
expect(memberRes.stats.maxHealth).to.equal(common.maxHealth);
@@ -134,6 +134,22 @@ describe('POST /group/:groupId/join', () => {
await expect(user.get('/user')).to.eventually.have.deep.property('items.quests.basilist', 1);
});
it('notifies inviting user that their invitation was accepted', async () => {
await invitedUser.post(`/groups/${guild._id}/join`);
let inviter = await user.get('/user');
let expectedData = {
headerText: t('invitationAcceptedHeader'),
bodyText: t('invitationAcceptedBody', {
username: invitedUser.auth.local.username,
groupName: guild.name,
}),
};
expect(inviter.notifications[0].type).to.eql('GROUP_INVITE_ACCEPTED');
expect(inviter.notifications[0].data).to.eql(expectedData);
});
});
});
@@ -172,6 +188,23 @@ describe('POST /group/:groupId/join', () => {
await expect(invitedUser.get('/user')).to.eventually.have.deep.property('party._id', party._id);
});
it('notifies inviting user that their invitation was accepted', async () => {
await invitedUser.post(`/groups/${party._id}/join`);
let inviter = await user.get('/user');
let expectedData = {
headerText: t('invitationAcceptedHeader'),
bodyText: t('invitationAcceptedBody', {
username: invitedUser.auth.local.username,
groupName: party.name,
}),
};
expect(inviter.notifications[0].type).to.eql('GROUP_INVITE_ACCEPTED');
expect(inviter.notifications[0].data).to.eql(expectedData);
});
it('clears invitation from user when joining party', async () => {
await invitedUser.post(`/groups/${party._id}/join`);
@@ -3,6 +3,7 @@ import {
createAndPopulateGroup,
translate as t,
} from '../../../../helpers/api-v3-integration.helper';
import * as email from '../../../../../website/server/libs/email';
describe('POST /groups/:groupId/removeMember/:memberId', () => {
let leader;
@@ -60,6 +61,14 @@ describe('POST /groups/:groupId/removeMember/:memberId', () => {
});
context('Guilds', () => {
beforeEach(() => {
sandbox.spy(email, 'sendTxn');
});
afterEach(() => {
sandbox.restore();
});
it('can remove other members', async () => {
await leader.post(`/groups/${guild._id}/removeMember/${member._id}`);
let memberRemoved = await member.get('/user');
@@ -80,6 +89,22 @@ describe('POST /groups/:groupId/removeMember/:memberId', () => {
expect(_.findIndex(invitedUserWithoutInvite.invitations.guilds, {id: guild._id})).eql(-1);
});
it('sends email to user with rescinded invite', async () => {
await leader.post(`/groups/${guild._id}/removeMember/${invitedUser._id}`);
expect(email.sendTxn).to.be.calledOnce;
expect(email.sendTxn.args[0][0]._id).to.be.eql(invitedUser._id);
expect(email.sendTxn.args[0][1]).to.be.eql('guild-invite-rescinded');
});
it('sends email to removed user', async () => {
await leader.post(`/groups/${guild._id}/removeMember/${member._id}`);
expect(email.sendTxn).to.be.calledOnce;
expect(email.sendTxn.args[0][0]._id).to.be.eql(member._id);
expect(email.sendTxn.args[0][1]).to.be.eql('kicked-from-guild');
});
});
context('Party', () => {
@@ -105,6 +130,11 @@ describe('POST /groups/:groupId/removeMember/:memberId', () => {
partyInvitedUser = invitees[0];
partyMember = members[0];
removedMember = members[1];
sandbox.spy(email, 'sendTxn');
});
afterEach(() => {
sandbox.restore();
});
it('can remove other members', async () => {
@@ -187,5 +217,21 @@ describe('POST /groups/:groupId/removeMember/:memberId', () => {
expect(party.quest.members[partyLeader._id]).to.be.true;
expect(party.quest.members[partyMember._id]).to.not.exist;
});
it('sends email to user with rescinded invite', async () => {
await partyLeader.post(`/groups/${party._id}/removeMember/${partyInvitedUser._id}`);
expect(email.sendTxn).to.be.calledOnce;
expect(email.sendTxn.args[0][0]._id).to.be.eql(partyInvitedUser._id);
expect(email.sendTxn.args[0][1]).to.be.eql('party-invite-rescinded');
});
it('sends email to removed user', async () => {
await partyLeader.post(`/groups/${party._id}/removeMember/${partyMember._id}`);
expect(email.sendTxn).to.be.calledOnce;
expect(email.sendTxn.args[0][0]._id).to.be.eql(partyMember._id);
expect(email.sendTxn.args[0][1]).to.be.eql('kicked-from-party');
});
});
});
@@ -300,6 +300,26 @@ describe('Post /groups/:groupId/invite', () => {
message: t('userAlreadyInGroup'),
});
});
// @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 () => {
let userToInvite = await generateUser();
let nonGroupLeader = await generateUser();
await inviter.post(`/groups/${group._id}/invite`, {
uuids: [nonGroupLeader._id],
});
await nonGroupLeader.post(`/groups/${group._id}/join`);
await expect(nonGroupLeader.post(`/groups/${group._id}/invite`, {
uuids: [userToInvite._id],
}))
.to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: t('onlyGroupLeaderCanInviteToGroupPlan'),
});
});
});
describe('party invites', () => {
@@ -40,13 +40,14 @@ describe('PUT /heroes/:heroId', () => {
});
});
it('updates contributor level, balance, ads, blocked', async () => {
it('change contributor level, balance, ads', async () => {
let hero = await generateUser();
let prevBlockState = hero.auth.blocked;
let prevSleepState = hero.preferences.sleep;
let heroRes = await user.put(`/hall/heroes/${hero._id}`, {
balance: 3,
contributor: {level: 1},
purchased: {ads: true},
auth: {blocked: true},
});
// test response
@@ -61,18 +62,47 @@ describe('PUT /heroes/:heroId', () => {
expect(heroRes.balance).to.equal(3 + 0.75); // 3+0.75 for first contrib level
expect(heroRes.contributor.level).to.equal(1);
expect(heroRes.purchased.ads).to.equal(true);
expect(heroRes.auth.blocked).to.equal(true);
// test hero values
await hero.sync();
expect(hero.balance).to.equal(3 + 0.75); // 3+0.75 for first contrib level
expect(hero.contributor.level).to.equal(1);
expect(hero.purchased.ads).to.equal(true);
expect(hero.auth.blocked).to.equal(true);
expect(hero.preferences.sleep).to.equal(true);
expect(hero.auth.blocked).to.equal(prevBlockState);
expect(hero.preferences.sleep).to.equal(prevSleepState);
expect(hero.notifications.length).to.equal(1);
expect(hero.notifications[0].type).to.equal('NEW_CONTRIBUTOR_LEVEL');
});
it('block a user', async () => {
let hero = await generateUser();
let heroRes = await user.put(`/hall/heroes/${hero._id}`, {
auth: {blocked: true},
preferences: {sleep: true},
});
// test response values
expect(heroRes.auth.blocked).to.equal(true);
// test hero values
await hero.sync();
expect(hero.auth.blocked).to.equal(true);
expect(hero.preferences.sleep).to.equal(true);
});
it('unblock a user', async () => {
let hero = await generateUser();
let prevSleepState = hero.preferences.sleep;
let heroRes = await user.put(`/hall/heroes/${hero._id}`, {
auth: {blocked: false},
});
// test response values
expect(heroRes.auth.blocked).to.equal(false);
// test hero values
await hero.sync();
expect(hero.auth.blocked).to.equal(false);
expect(hero.preferences.sleep).to.equal(prevSleepState);
});
it('updates chatRevoked flag', async () => {
let hero = await generateUser();
@@ -0,0 +1,44 @@
import {
generateUser,
translate as t,
} from '../../../../helpers/api-v3-integration.helper';
import { v4 as generateUUID } from 'uuid';
describe('GET /members/:memberId/achievements', () => {
let user;
before(async () => {
user = await generateUser();
});
it('validates req.params.memberId', async () => {
await expect(user.get('/members/invalidUUID/achievements')).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('invalidReqParams'),
});
});
it('returns achievements based on given user', async () => {
let member = await generateUser({
contributor: {level: 1},
backer: {tier: 3},
});
let achievementsRes = await user.get(`/members/${member._id}/achievements`);
expect(achievementsRes.special.achievements.contributor.earned).to.equal(true);
expect(achievementsRes.special.achievements.contributor.value).to.equal(1);
expect(achievementsRes.special.achievements.kickstarter.earned).to.equal(true);
expect(achievementsRes.special.achievements.kickstarter.value).to.equal(3);
});
it('handles non-existing members', async () => {
let dummyId = generateUUID();
await expect(user.get(`/members/${dummyId}/achievements`)).to.eventually.be.rejected.and.eql({
code: 404,
error: 'NotFound',
message: t('userWithIDNotFound', {userId: dummyId}),
});
});
});
@@ -35,8 +35,10 @@ describe('GET /members/:memberId', () => {
'backer', 'contributor', 'auth', 'items', 'inbox',
]);
expect(Object.keys(memberRes.auth)).to.eql(['timestamps']);
expect(Object.keys(memberRes.preferences).sort()).to.eql(['size', 'hair', 'skin', 'shirt',
'chair', 'costume', 'sleep', 'background'].sort());
expect(Object.keys(memberRes.preferences).sort()).to.eql([
'size', 'hair', 'skin', 'shirt',
'chair', 'costume', 'sleep', 'background',
].sort());
expect(memberRes.stats.maxMP).to.exist;
expect(memberRes.stats.maxHealth).to.equal(common.maxHealth);
@@ -4,6 +4,14 @@ import {
} from '../../../../helpers/api-v3-integration.helper';
import { v4 as generateUUID } from 'uuid';
function findMessage (messages, receiverId) {
let message = _.find(messages, (inboxMessage) => {
return inboxMessage.uuid === receiverId;
});
return message;
}
describe('POST /members/transfer-gems', () => {
let userToSendMessage;
let receiver;
@@ -116,19 +124,14 @@ describe('POST /members/transfer-gems', () => {
let updatedReceiver = await receiver.get('/user');
let updatedSender = await userToSendMessage.get('/user');
let sendersMessageInReceiversInbox = _.find(updatedReceiver.inbox.messages, (inboxMessage) => {
return inboxMessage.uuid === userToSendMessage._id;
});
let sendersMessageInReceiversInbox = findMessage(updatedReceiver.inbox.messages, userToSendMessage._id);
let sendersMessageInSendersInbox = findMessage(updatedSender.inbox.messages, receiver._id);
let sendersMessageInSendersInbox = _.find(updatedSender.inbox.messages, (inboxMessage) => {
return inboxMessage.uuid === receiver._id;
});
let messageSentContent = t('privateMessageGiftIntro', {
let messageSentContent = t('privateMessageGiftGemsMessage', {
receiverName: receiver.profile.name,
senderName: userToSendMessage.profile.name,
gemAmount,
});
messageSentContent += t('privateMessageGiftGemsMessage', {gemAmount});
messageSentContent = `\`${messageSentContent}\` `;
messageSentContent += message;
@@ -150,19 +153,14 @@ describe('POST /members/transfer-gems', () => {
let updatedReceiver = await receiver.get('/user');
let updatedSender = await userToSendMessage.get('/user');
let sendersMessageInReceiversInbox = _.find(updatedReceiver.inbox.messages, (inboxMessage) => {
return inboxMessage.uuid === userToSendMessage._id;
});
let sendersMessageInReceiversInbox = findMessage(updatedReceiver.inbox.messages, userToSendMessage._id);
let sendersMessageInSendersInbox = findMessage(updatedSender.inbox.messages, receiver._id);
let sendersMessageInSendersInbox = _.find(updatedSender.inbox.messages, (inboxMessage) => {
return inboxMessage.uuid === receiver._id;
});
let messageSentContent = t('privateMessageGiftIntro', {
let messageSentContent = t('privateMessageGiftGemsMessage', {
receiverName: receiver.profile.name,
senderName: userToSendMessage.profile.name,
gemAmount,
});
messageSentContent += t('privateMessageGiftGemsMessage', {gemAmount});
messageSentContent = `\`${messageSentContent}\` `;
expect(sendersMessageInReceiversInbox).to.exist;
@@ -173,4 +171,40 @@ describe('POST /members/transfer-gems', () => {
expect(sendersMessageInSendersInbox.text).to.equal(messageSentContent);
expect(updatedSender.balance).to.equal(0);
});
it('sends transfer gems message in each participant\'s language', async () => {
await receiver.update({
'preferences.language': 'es',
});
await userToSendMessage.update({
'preferences.language': 'cs',
});
await userToSendMessage.post('/members/transfer-gems', {
gemAmount,
toUserId: receiver._id,
});
let updatedReceiver = await receiver.get('/user');
let updatedSender = await userToSendMessage.get('/user');
let sendersMessageInReceiversInbox = findMessage(updatedReceiver.inbox.messages, userToSendMessage._id);
let sendersMessageInSendersInbox = findMessage(updatedSender.inbox.messages, receiver._id);
let [receieversMessageContent, sendersMessageContent] = ['es', 'cs'].map((lang) => {
let messageContent = t('privateMessageGiftGemsMessage', {
receiverName: receiver.profile.name,
senderName: userToSendMessage.profile.name,
gemAmount,
}, lang);
return `\`${messageContent}\` `;
});
expect(sendersMessageInReceiversInbox).to.exist;
expect(sendersMessageInReceiversInbox.text).to.equal(receieversMessageContent);
expect(sendersMessageInSendersInbox).to.exist;
expect(sendersMessageInSendersInbox.text).to.equal(sendersMessageContent);
expect(updatedSender.balance).to.equal(0);
});
});
@@ -10,7 +10,7 @@ describe('GET /models/:model/paths', () => {
user = await generateUser();
});
it('returns an error when model is not accessible or doesn\'t exists', async () => {
it('returns an error when model is not accessible or doesn\'t exist', async () => {
await expect(user.get('/models/1234/paths')).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
@@ -0,0 +1,26 @@
import {
generateUser,
translate as t,
} from '../../../../helpers/api-v3-integration.helper';
import { v4 as generateUUID } from 'uuid';
describe('POST /notifications/:notificationId/read', () => {
let user;
before(async () => {
user = await generateUser();
});
it('errors when notification is not found', async () => {
let dummyId = generateUUID();
await expect(user.post(`/notifications/${dummyId}/read`))
.to.eventually.be.rejected.and.eql({
code: 404,
error: 'NotFound',
message: t('messageNotificationNotFound'),
});
});
xit('removes a notification', async () => {
});
});
@@ -1,21 +0,0 @@
import {
generateUser,
translate as t,
} from '../../../../helpers/api-integration/v3';
describe('payments : amazon #subscribeCancel', () => {
let endpoint = '/amazon/subscribe/cancel';
let user;
beforeEach(async () => {
user = await generateUser();
});
it('verifies subscription', async () => {
await expect(user.get(endpoint)).to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: t('missingSubscription'),
});
});
});
@@ -1,21 +0,0 @@
import {
generateUser,
translate as t,
} from '../../../../helpers/api-integration/v3';
xdescribe('payments : paypal #checkout', () => {
let endpoint = '/paypal/checkout';
let user;
beforeEach(async () => {
user = await generateUser();
});
it('verifies subscription', async () => {
await expect(user.get(endpoint)).to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: t('missingSubscription'),
});
});
});
@@ -1,21 +0,0 @@
import {
generateUser,
translate as t,
} from '../../../../helpers/api-integration/v3';
xdescribe('payments : paypal #checkoutSuccess', () => {
let endpoint = '/paypal/checkout/success';
let user;
beforeEach(async () => {
user = await generateUser();
});
it('verifies subscription', async () => {
await expect(user.get(endpoint)).to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: t('missingSubscription'),
});
});
});
@@ -1,21 +0,0 @@
import {
generateUser,
translate as t,
} from '../../../../helpers/api-integration/v3';
xdescribe('payments : paypal #subscribe', () => {
let endpoint = '/paypal/subscribe';
let user;
beforeEach(async () => {
user = await generateUser();
});
it('verifies credentials', async () => {
await expect(user.get(endpoint)).to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: t('missingSubscription'),
});
});
});
@@ -1,21 +0,0 @@
import {
generateUser,
translate as t,
} from '../../../../helpers/api-integration/v3';
describe('payments : paypal #subscribeCancel', () => {
let endpoint = '/paypal/subscribe/cancel';
let user;
beforeEach(async () => {
user = await generateUser();
});
it('verifies credentials', async () => {
await expect(user.get(endpoint)).to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: t('missingSubscription'),
});
});
});
@@ -1,21 +0,0 @@
import {
generateUser,
translate as t,
} from '../../../../helpers/api-integration/v3';
xdescribe('payments : paypal #subscribeSuccess', () => {
let endpoint = '/paypal/subscribe/success';
let user;
beforeEach(async () => {
user = await generateUser();
});
it('verifies credentials', async () => {
await expect(user.get(endpoint)).to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: t('missingSubscription'),
});
});
});
@@ -1,21 +0,0 @@
import {
generateUser,
translate as t,
} from '../../../../helpers/api-integration/v3';
describe('payments - stripe - #subscribeCancel', () => {
let endpoint = '/stripe/subscribe/cancel';
let user;
beforeEach(async () => {
user = await generateUser();
});
it('verifies credentials', async () => {
await expect(user.get(endpoint)).to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: t('missingSubscription'),
});
});
});
@@ -1,20 +0,0 @@
import {
generateUser,
} from '../../../../helpers/api-integration/v3';
describe('payments - amazon - #checkout', () => {
let endpoint = '/amazon/checkout';
let user;
beforeEach(async () => {
user = await generateUser();
});
it('verifies credentials', async () => {
await expect(user.post(endpoint)).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: 'Missing req.body.orderReferenceId',
});
});
});
@@ -1,22 +0,0 @@
import {
generateUser,
} from '../../../../helpers/api-integration/v3';
describe('payments - amazon - #createOrderReferenceId', () => {
let endpoint = '/amazon/createOrderReferenceId';
let user;
beforeEach(async () => {
user = await generateUser();
});
it('verifies billingAgreementId', async (done) => {
try {
await user.post(endpoint);
} catch (e) {
// Parameter AWSAccessKeyId cannot be empty.
expect(e.error).to.eql('BadRequest');
done();
}
});
});
@@ -1,21 +0,0 @@
import {
generateUser,
translate as t,
} from '../../../../helpers/api-integration/v3';
describe('payments - amazon - #subscribe', () => {
let endpoint = '/amazon/subscribe';
let user;
beforeEach(async () => {
user = await generateUser();
});
it('verifies subscription code', async () => {
await expect(user.post(endpoint)).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('missingSubscriptionCode'),
});
});
});
@@ -1,17 +0,0 @@
import {
generateUser,
} from '../../../../helpers/api-integration/v3';
describe('payments - paypal - #ipn', () => {
let endpoint = '/paypal/ipn';
let user;
beforeEach(async () => {
user = await generateUser();
});
it('verifies credentials', async () => {
let result = await user.post(endpoint);
expect(result).to.eql('OK');
});
});
@@ -1,20 +0,0 @@
import {
generateUser,
} from '../../../../helpers/api-integration/v3';
describe('payments - stripe - #checkout', () => {
let endpoint = '/stripe/checkout';
let user;
beforeEach(async () => {
user = await generateUser();
});
it('verifies credentials', async () => {
await expect(user.post(endpoint, {id: 123})).to.eventually.be.rejected.and.eql({
code: 401,
error: 'Error',
message: 'Invalid API Key provided: ****************************1111',
});
});
});
@@ -1,21 +0,0 @@
import {
generateUser,
translate as t,
} from '../../../../helpers/api-integration/v3';
describe('payments - stripe - #subscribeEdit', () => {
let endpoint = '/stripe/subscribe/edit';
let user;
beforeEach(async () => {
user = await generateUser();
});
it('verifies credentials', async () => {
await expect(user.post(endpoint)).to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: t('missingSubscription'),
});
});
});
@@ -0,0 +1,78 @@
import {
generateUser,
generateGroup,
translate as t,
} from '../../../../../helpers/api-integration/v3';
import amzLib from '../../../../../../website/server/libs/amazonPayments';
describe('payments : amazon #subscribeCancel', () => {
let endpoint = '/amazon/subscribe/cancel?noRedirect=true';
let user, group, amazonSubscribeCancelStub;
beforeEach(async () => {
user = await generateUser();
});
it('throws error when there users has no subscription', async () => {
await expect(user.get(endpoint)).to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: t('missingSubscription'),
});
});
describe('success', () => {
beforeEach(() => {
amazonSubscribeCancelStub = sinon.stub(amzLib, 'cancelSubscription').returnsPromise().resolves({});
});
afterEach(() => {
amzLib.cancelSubscription.restore();
});
it('cancels a user subscription', async () => {
user = await generateUser({
'profile.name': 'sender',
'purchased.plan.customerId': 'customer-id',
'purchased.plan.planId': 'basic_3mo',
'purchased.plan.lastBillingDate': new Date(),
balance: 2,
});
await user.get(endpoint);
expect(amazonSubscribeCancelStub).to.be.calledOnce;
expect(amazonSubscribeCancelStub.args[0][0].user._id).to.eql(user._id);
expect(amazonSubscribeCancelStub.args[0][0].groupId).to.eql(undefined);
expect(amazonSubscribeCancelStub.args[0][0].headers['x-api-key']).to.eql(user.apiToken);
expect(amazonSubscribeCancelStub.args[0][0].headers['x-api-user']).to.eql(user._id);
});
it('cancels a group subscription', async () => {
user = await generateUser({
'profile.name': 'sender',
'purchased.plan.customerId': 'customer-id',
'purchased.plan.planId': 'basic_3mo',
'purchased.plan.lastBillingDate': new Date(),
balance: 2,
});
group = await generateGroup(user, {
name: 'test group',
type: 'guild',
privacy: 'public',
'purchased.plan.customerId': 'customer-id',
'purchased.plan.planId': 'basic_3mo',
'purchased.plan.lastBillingDate': new Date(),
});
await user.get(`${endpoint}&groupId=${group._id}`);
expect(amazonSubscribeCancelStub).to.be.calledOnce;
expect(amazonSubscribeCancelStub.args[0][0].user._id).to.eql(user._id);
expect(amazonSubscribeCancelStub.args[0][0].groupId).to.eql(group._id);
expect(amazonSubscribeCancelStub.args[0][0].headers['x-api-key']).to.eql(user.apiToken);
expect(amazonSubscribeCancelStub.args[0][0].headers['x-api-user']).to.eql(user._id);
});
});
});
@@ -0,0 +1,63 @@
import {
generateUser,
} from '../../../../../helpers/api-integration/v3';
import amzLib from '../../../../../../website/server/libs/amazonPayments';
describe('payments - amazon - #checkout', () => {
let endpoint = '/amazon/checkout';
let user, amazonCheckoutStub;
beforeEach(async () => {
user = await generateUser();
});
it('verifies credentials', async () => {
await expect(user.post(endpoint)).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: 'Missing req.body.orderReferenceId',
});
});
describe('success', () => {
beforeEach(async () => {
amazonCheckoutStub = sinon.stub(amzLib, 'checkout').returnsPromise().resolves({});
});
afterEach(() => {
amzLib.checkout.restore();
});
it('makes a purcahse with amazon checkout', async () => {
user = await generateUser({
'profile.name': 'sender',
'purchased.plan.customerId': 'customer-id',
'purchased.plan.planId': 'basic_3mo',
'purchased.plan.lastBillingDate': new Date(),
balance: 2,
});
let gift = {
type: 'gems',
gems: {
amount: 16,
uuid: user._id,
},
};
let orderReferenceId = 'orderReferenceId-example';
await user.post(endpoint, {
gift,
orderReferenceId,
});
expect(amazonCheckoutStub).to.be.calledOnce;
expect(amazonCheckoutStub.args[0][0].user._id).to.eql(user._id);
expect(amazonCheckoutStub.args[0][0].gift).to.eql(gift);
expect(amazonCheckoutStub.args[0][0].orderReferenceId).to.eql(orderReferenceId);
expect(amazonCheckoutStub.args[0][0].headers['x-api-key']).to.eql(user.apiToken);
expect(amazonCheckoutStub.args[0][0].headers['x-api-user']).to.eql(user._id);
});
});
});
@@ -0,0 +1,20 @@
import {
generateUser,
} from '../../../../../helpers/api-integration/v3';
describe('payments - amazon - #createOrderReferenceId', () => {
let endpoint = '/amazon/createOrderReferenceId';
let user;
beforeEach(async () => {
user = await generateUser();
});
it('verifies billingAgreementId', async () => {
await expect(user.post(endpoint)).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: 'Missing req.body.billingAgreementId',
});
});
});
@@ -0,0 +1,96 @@
import {
generateUser,
generateGroup,
translate as t,
} from '../../../../../helpers/api-integration/v3';
import amzLib from '../../../../../../website/server/libs/amazonPayments';
describe('payments - amazon - #subscribe', () => {
let endpoint = '/amazon/subscribe';
let user, group, subscribeWithAmazonStub;
beforeEach(async () => {
user = await generateUser();
});
it('verifies subscription code', async () => {
await expect(user.post(endpoint)).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('missingSubscriptionCode'),
});
});
describe('success', () => {
let billingAgreementId = 'billingAgreementId-example';
let subscription = 'basic_3mo';
let coupon;
beforeEach(() => {
subscribeWithAmazonStub = sinon.stub(amzLib, 'subscribe').returnsPromise().resolves({});
});
afterEach(() => {
amzLib.subscribe.restore();
});
it('creates a user subscription', async () => {
user = await generateUser({
'profile.name': 'sender',
'purchased.plan.customerId': 'customer-id',
'purchased.plan.planId': 'basic_3mo',
'purchased.plan.lastBillingDate': new Date(),
balance: 2,
});
await user.post(endpoint, {
billingAgreementId,
subscription,
coupon,
});
expect(subscribeWithAmazonStub).to.be.calledOnce;
expect(subscribeWithAmazonStub.args[0][0].billingAgreementId).to.eql(billingAgreementId);
expect(subscribeWithAmazonStub.args[0][0].sub).to.exist;
expect(subscribeWithAmazonStub.args[0][0].coupon).to.eql(coupon);
expect(subscribeWithAmazonStub.args[0][0].groupId).not.exist;
expect(subscribeWithAmazonStub.args[0][0].headers['x-api-key']).to.eql(user.apiToken);
expect(subscribeWithAmazonStub.args[0][0].headers['x-api-user']).to.eql(user._id);
});
it('creates a group subscription', async () => {
user = await generateUser({
'profile.name': 'sender',
'purchased.plan.customerId': 'customer-id',
'purchased.plan.planId': 'basic_3mo',
'purchased.plan.lastBillingDate': new Date(),
balance: 2,
});
group = await generateGroup(user, {
name: 'test group',
type: 'guild',
privacy: 'public',
'purchased.plan.customerId': 'customer-id',
'purchased.plan.planId': 'basic_3mo',
'purchased.plan.lastBillingDate': new Date(),
});
await user.post(endpoint, {
billingAgreementId,
subscription,
coupon,
groupId: group._id,
});
expect(subscribeWithAmazonStub).to.be.calledOnce;
expect(subscribeWithAmazonStub.args[0][0].billingAgreementId).to.eql(billingAgreementId);
expect(subscribeWithAmazonStub.args[0][0].sub).to.exist;
expect(subscribeWithAmazonStub.args[0][0].coupon).to.eql(coupon);
expect(subscribeWithAmazonStub.args[0][0].user._id).to.eql(user._id);
expect(subscribeWithAmazonStub.args[0][0].groupId).to.eql(group._id);
expect(subscribeWithAmazonStub.args[0][0].headers['x-api-key']).to.eql(user.apiToken);
expect(subscribeWithAmazonStub.args[0][0].headers['x-api-user']).to.eql(user._id);
});
});
});
@@ -1,6 +1,6 @@
import {
generateUser,
} from '../../../../helpers/api-integration/v3';
} from '../../../../../helpers/api-integration/v3';
describe('payments : amazon', () => {
let endpoint = '/amazon/verifyAccessToken';
@@ -0,0 +1,41 @@
import {generateUser} from '../../../../../helpers/api-integration/v3';
import applePayments from '../../../../../../website/server/libs/applePayments';
describe('payments : apple #cancelSubscribe', () => {
let endpoint = '/iap/ios/subscribe/cancel?noRedirect=true';
let user;
beforeEach(async () => {
user = await generateUser();
});
describe('success', () => {
let cancelStub;
beforeEach(async () => {
cancelStub = sinon.stub(applePayments, 'cancelSubscribe').returnsPromise().resolves({});
});
afterEach(() => {
applePayments.cancelSubscribe.restore();
});
it('cancels the subscription', async () => {
user = await generateUser({
'profile.name': 'sender',
'purchased.plan.paymentMethod': 'Apple',
'purchased.plan.customerId': 'customer-id',
'purchased.plan.planId': 'basic_3mo',
'purchased.plan.lastBillingDate': new Date(),
balance: 2,
});
await user.get(endpoint);
expect(cancelStub).to.be.calledOnce;
expect(cancelStub.args[0][0]._id).to.eql(user._id);
expect(cancelStub.args[0][1]['x-api-key']).to.eql(user.apiToken);
expect(cancelStub.args[0][1]['x-api-user']).to.eql(user._id);
});
});
});
@@ -0,0 +1,40 @@
import {generateUser} from '../../../../../helpers/api-integration/v3';
import applePayments from '../../../../../../website/server/libs/applePayments';
describe('payments : apple #verify', () => {
let endpoint = '/iap/ios/verify';
let user;
beforeEach(async () => {
user = await generateUser();
});
describe('success', () => {
let verifyStub;
beforeEach(async () => {
verifyStub = sinon.stub(applePayments, 'verifyGemPurchase').returnsPromise().resolves({});
});
afterEach(() => {
applePayments.verifyGemPurchase.restore();
});
it('makes a purchase', async () => {
user = await generateUser({
balance: 2,
});
await user.post(endpoint, {
transaction: {
receipt: 'receipt',
}});
expect(verifyStub).to.be.calledOnce;
expect(verifyStub.args[0][0]._id).to.eql(user._id);
expect(verifyStub.args[0][1]).to.eql('receipt');
expect(verifyStub.args[0][2]['x-api-key']).to.eql(user.apiToken);
expect(verifyStub.args[0][2]['x-api-user']).to.eql(user._id);
});
});
});
@@ -0,0 +1,55 @@
import {generateUser, translate as t} from '../../../../../helpers/api-integration/v3';
import applePayments from '../../../../../../website/server/libs/applePayments';
describe('payments : apple #subscribe', () => {
let endpoint = '/iap/ios/subscribe';
let user;
beforeEach(async () => {
user = await generateUser();
});
it('verifies sub key', async () => {
await expect(user.post(endpoint)).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('missingSubscriptionCode'),
});
});
describe('success', () => {
let subscribeStub;
beforeEach(async () => {
subscribeStub = sinon.stub(applePayments, 'subscribe').returnsPromise().resolves({});
});
afterEach(() => {
applePayments.subscribe.restore();
});
it('makes a purchase', async () => {
user = await generateUser({
'profile.name': 'sender',
'purchased.plan.customerId': 'customer-id',
'purchased.plan.planId': 'basic_3mo',
'purchased.plan.lastBillingDate': new Date(),
balance: 2,
});
let sku = 'com.habitrpg.ios.habitica.subscription.3month';
await user.post(endpoint, {
sku,
receipt: 'receipt',
});
expect(subscribeStub).to.be.calledOnce;
expect(subscribeStub.args[0][0]).to.eql(sku);
expect(subscribeStub.args[0][1]._id).to.eql(user._id);
expect(subscribeStub.args[0][2]).to.eql('receipt');
expect(subscribeStub.args[0][3]['x-api-key']).to.eql(user.apiToken);
expect(subscribeStub.args[0][3]['x-api-user']).to.eql(user._id);
});
});
});
@@ -0,0 +1,41 @@
import {generateUser} from '../../../../../helpers/api-integration/v3';
import googlePayments from '../../../../../../website/server/libs/googlePayments';
describe('payments : google #cancelSubscribe', () => {
let endpoint = '/iap/android/subscribe/cancel?noRedirect=true';
let user;
beforeEach(async () => {
user = await generateUser();
});
describe('success', () => {
let cancelStub;
beforeEach(async () => {
cancelStub = sinon.stub(googlePayments, 'cancelSubscribe').returnsPromise().resolves({});
});
afterEach(() => {
googlePayments.cancelSubscribe.restore();
});
it('makes a purchase', async () => {
user = await generateUser({
'profile.name': 'sender',
'purchased.plan.paymentMethod': 'Google',
'purchased.plan.customerId': 'customer-id',
'purchased.plan.planId': 'basic_3mo',
'purchased.plan.lastBillingDate': new Date(),
balance: 2,
});
await user.get(endpoint);
expect(cancelStub).to.be.calledOnce;
expect(cancelStub.args[0][0]._id).to.eql(user._id);
expect(cancelStub.args[0][1]['x-api-key']).to.eql(user.apiToken);
expect(cancelStub.args[0][1]['x-api-user']).to.eql(user._id);
});
});
});
@@ -0,0 +1,56 @@
import {generateUser, translate as t} from '../../../../../helpers/api-integration/v3';
import googlePayments from '../../../../../../website/server/libs/googlePayments';
describe('payments : google #subscribe', () => {
let endpoint = '/iap/android/subscribe';
let user;
beforeEach(async () => {
user = await generateUser();
});
it('verifies sub key', async () => {
await expect(user.post(endpoint)).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('missingSubscriptionCode'),
});
});
describe('success', () => {
let subscribeStub;
beforeEach(async () => {
subscribeStub = sinon.stub(googlePayments, 'subscribe').returnsPromise().resolves({});
});
afterEach(() => {
googlePayments.subscribe.restore();
});
it('makes a purchase', async () => {
user = await generateUser({
'profile.name': 'sender',
'purchased.plan.customerId': 'customer-id',
'purchased.plan.planId': 'basic_3mo',
'purchased.plan.lastBillingDate': new Date(),
balance: 2,
});
let sku = 'com.habitrpg.android.habitica.subscription.3month';
await user.post(endpoint, {
sku,
transaction: {receipt: 'receipt', signature: 'signature'},
});
expect(subscribeStub).to.be.calledOnce;
expect(subscribeStub.args[0][0]).to.eql(sku);
expect(subscribeStub.args[0][1]._id).to.eql(user._id);
expect(subscribeStub.args[0][2]).to.eql('receipt');
expect(subscribeStub.args[0][3]).to.eql('signature');
expect(subscribeStub.args[0][4]['x-api-key']).to.eql(user.apiToken);
expect(subscribeStub.args[0][4]['x-api-user']).to.eql(user._id);
});
});
});
@@ -0,0 +1,40 @@
import {generateUser} from '../../../../../helpers/api-integration/v3';
import googlePayments from '../../../../../../website/server/libs/googlePayments';
describe('payments : google #verify', () => {
let endpoint = '/iap/android/verify';
let user;
beforeEach(async () => {
user = await generateUser();
});
describe('success', () => {
let verifyStub;
beforeEach(async () => {
verifyStub = sinon.stub(googlePayments, 'verifyGemPurchase').returnsPromise().resolves({});
});
afterEach(() => {
googlePayments.verifyGemPurchase.restore();
});
it('makes a purchase', async () => {
user = await generateUser({
balance: 2,
});
await user.post(endpoint, {
transaction: {receipt: 'receipt', signature: 'signature'},
});
expect(verifyStub).to.be.calledOnce;
expect(verifyStub.args[0][0]._id).to.eql(user._id);
expect(verifyStub.args[0][1]).to.eql('receipt');
expect(verifyStub.args[0][2]).to.eql('signature');
expect(verifyStub.args[0][3]['x-api-key']).to.eql(user.apiToken);
expect(verifyStub.args[0][3]['x-api-user']).to.eql(user._id);
});
});
});
@@ -0,0 +1,40 @@
import {
generateUser,
} from '../../../../../helpers/api-integration/v3';
import paypalPayments from '../../../../../../website/server/libs/paypalPayments';
describe('payments : paypal #checkout', () => {
let endpoint = '/paypal/checkout';
let user;
beforeEach(async () => {
user = await generateUser();
});
describe('success', () => {
let checkoutStub;
beforeEach(async () => {
checkoutStub = sinon.stub(paypalPayments, 'checkout').returnsPromise().resolves('/');
});
afterEach(() => {
paypalPayments.checkout.restore();
});
it('creates a purchase link', async () => {
user = await generateUser({
'profile.name': 'sender',
'purchased.plan.customerId': 'customer-id',
'purchased.plan.planId': 'basic_3mo',
'purchased.plan.lastBillingDate': new Date(),
balance: 2,
});
await user.get(`${endpoint}?noRedirect=true`);
expect(checkoutStub).to.be.calledOnce;
expect(checkoutStub.args[0][0].gift).to.eql(undefined);
});
});
});
@@ -0,0 +1,66 @@
import {
generateUser,
translate as t,
} from '../../../../../helpers/api-integration/v3';
import paypalPayments from '../../../../../../website/server/libs/paypalPayments';
describe('payments : paypal #checkoutSuccess', () => {
let endpoint = '/paypal/checkout/success';
let user;
beforeEach(async () => {
user = await generateUser();
});
it('verifies paymentId', async () => {
await expect(user.get(endpoint))
.to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('missingPaymentId'),
});
});
it('verifies customerId', async () => {
await expect(user.get(`${endpoint}?paymentId=test-paymentid`))
.to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('missingCustomerId'),
});
});
describe('success', () => {
let checkoutSuccessStub;
beforeEach(async () => {
checkoutSuccessStub = sinon.stub(paypalPayments, 'checkoutSuccess').returnsPromise().resolves({});
});
afterEach(() => {
paypalPayments.checkoutSuccess.restore();
});
it('makes a purchase', async () => {
let paymentId = 'test-paymentid';
let customerId = 'test-customerId';
user = await generateUser({
'profile.name': 'sender',
'purchased.plan.customerId': 'customer-id',
'purchased.plan.planId': 'basic_3mo',
'purchased.plan.lastBillingDate': new Date(),
balance: 2,
});
await user.get(`${endpoint}?PayerID=${customerId}&paymentId=${paymentId}&noRedirect=true`);
expect(checkoutSuccessStub).to.be.calledOnce;
expect(checkoutSuccessStub.args[0][0].user._id).to.eql(user._id);
expect(checkoutSuccessStub.args[0][0].gift).to.eql(undefined);
expect(checkoutSuccessStub.args[0][0].paymentId).to.eql(paymentId);
expect(checkoutSuccessStub.args[0][0].customerId).to.eql(customerId);
});
});
});
@@ -0,0 +1,55 @@
import {
generateUser,
translate as t,
} from '../../../../../helpers/api-integration/v3';
import paypalPayments from '../../../../../../website/server/libs/paypalPayments';
import shared from '../../../../../../website/common';
describe('payments : paypal #subscribe', () => {
let endpoint = '/paypal/subscribe';
let user;
beforeEach(async () => {
user = await generateUser();
});
it('verifies sub key', async () => {
await expect(user.get(endpoint)).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('missingSubKey'),
});
});
describe('success', () => {
let subscribeStub;
beforeEach(async () => {
subscribeStub = sinon.stub(paypalPayments, 'subscribe').returnsPromise().resolves('/');
});
afterEach(() => {
paypalPayments.subscribe.restore();
});
it('makes a purchase', async () => {
let subKey = 'basic_3mo';
let sub = shared.content.subscriptionBlocks[subKey];
user = await generateUser({
'profile.name': 'sender',
'purchased.plan.customerId': 'customer-id',
'purchased.plan.planId': 'basic_3mo',
'purchased.plan.lastBillingDate': new Date(),
balance: 2,
});
await user.get(`${endpoint}?sub=${subKey}&noRedirect=true`);
expect(subscribeStub).to.be.calledOnce;
expect(subscribeStub.args[0][0].sub).to.eql(sub);
expect(subscribeStub.args[0][0].coupon).to.eql(undefined);
});
});
});
@@ -0,0 +1,52 @@
import {
generateUser,
translate as t,
} from '../../../../../helpers/api-integration/v3';
import paypalPayments from '../../../../../../website/server/libs/paypalPayments';
describe('payments : paypal #subscribeCancel', () => {
let endpoint = '/paypal/subscribe/cancel';
let user;
beforeEach(async () => {
user = await generateUser();
});
it('verifies credentials', async () => {
await expect(user.get(endpoint))
.to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: t('missingSubscription'),
});
});
describe('success', () => {
let subscribeCancelStub;
beforeEach(async () => {
subscribeCancelStub = sinon.stub(paypalPayments, 'subscribeCancel').returnsPromise().resolves('/');
});
afterEach(() => {
paypalPayments.subscribeCancel.restore();
});
it('cancels a subscription', async () => {
user = await generateUser({
'profile.name': 'sender',
'purchased.plan.customerId': 'customer-id',
'purchased.plan.planId': 'basic_3mo',
'purchased.plan.lastBillingDate': new Date(),
balance: 2,
});
await user.get(`${endpoint}?noRedirect=true`);
expect(subscribeCancelStub).to.be.calledOnce;
expect(subscribeCancelStub.args[0][0].user._id).to.eql(user._id);
expect(subscribeCancelStub.args[0][0].groupId).to.eql(undefined);
});
});
});
@@ -0,0 +1,56 @@
import {
generateUser,
translate as t,
} from '../../../../../helpers/api-integration/v3';
import paypalPayments from '../../../../../../website/server/libs/paypalPayments';
describe('payments : paypal #subscribeSuccess', () => {
let endpoint = '/paypal/subscribe/success';
let user;
beforeEach(async () => {
user = await generateUser();
});
it('verifies Paypal Block', async () => {
await expect(user.get(endpoint)).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('missingPaypalBlock'),
});
});
xdescribe('success', () => {
let subscribeSuccessStub;
beforeEach(async () => {
subscribeSuccessStub = sinon.stub(paypalPayments, 'subscribeSuccess').returnsPromise().resolves({});
});
afterEach(() => {
paypalPayments.subscribeSuccess.restore();
});
it('creates a subscription', async () => {
let token = 'test-token';
user = await generateUser({
'profile.name': 'sender',
'purchased.plan.customerId': 'customer-id',
'purchased.plan.planId': 'basic_3mo',
'purchased.plan.lastBillingDate': new Date(),
balance: 2,
});
await user.get(`${endpoint}?token=${token}&noRedirect=true`);
expect(subscribeSuccessStub).to.be.calledOnce;
expect(subscribeSuccessStub.args[0][0].user._id).to.eql(user._id);
expect(subscribeSuccessStub.args[0][0].block).to.eql(undefined);
expect(subscribeSuccessStub.args[0][0].groupId).to.eql(undefined);
expect(subscribeSuccessStub.args[0][0].token).to.eql(token);
expect(subscribeSuccessStub.args[0][0].headers).to.exist;
});
});
});
@@ -0,0 +1,44 @@
import {
generateUser,
} from '../../../../../helpers/api-integration/v3';
import paypalPayments from '../../../../../../website/server/libs/paypalPayments';
describe('payments - paypal - #ipn', () => {
let endpoint = '/paypal/ipn';
let user;
beforeEach(async () => {
user = await generateUser();
});
it('verifies credentials', async () => {
let result = await user.post(endpoint);
expect(result).to.eql('OK');
});
describe('success', () => {
let ipnStub;
beforeEach(async () => {
ipnStub = sinon.stub(paypalPayments, 'ipn').returnsPromise().resolves({});
});
afterEach(() => {
paypalPayments.ipn.restore();
});
it('makes a purchase', async () => {
user = await generateUser({
'profile.name': 'sender',
'purchased.plan.customerId': 'customer-id',
'purchased.plan.planId': 'basic_3mo',
'purchased.plan.lastBillingDate': new Date(),
balance: 2,
});
await user.post(endpoint);
expect(ipnStub).to.be.calledOnce;
});
});
});
@@ -0,0 +1,74 @@
import {
generateUser,
generateGroup,
translate as t,
} from '../../../../../helpers/api-integration/v3';
import stripePayments from '../../../../../../website/server/libs/stripePayments';
describe('payments - stripe - #subscribeCancel', () => {
let endpoint = '/stripe/subscribe/cancel?redirect=none';
let user, group, stripeCancelSubscriptionStub;
beforeEach(async () => {
user = await generateUser();
});
it('verifies credentials', async () => {
await expect(user.get(endpoint)).to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: t('missingSubscription'),
});
});
describe('success', () => {
beforeEach(async () => {
stripeCancelSubscriptionStub = sinon.stub(stripePayments, 'cancelSubscription').returnsPromise().resolves({});
});
afterEach(() => {
stripePayments.cancelSubscription.restore();
});
it('cancels a user subscription', async () => {
user = await generateUser({
'profile.name': 'sender',
'purchased.plan.customerId': 'customer-id',
'purchased.plan.planId': 'basic_3mo',
'purchased.plan.lastBillingDate': new Date(),
balance: 2,
});
await user.get(`${endpoint}`);
expect(stripeCancelSubscriptionStub).to.be.calledOnce;
expect(stripeCancelSubscriptionStub.args[0][0].user._id).to.eql(user._id);
expect(stripeCancelSubscriptionStub.args[0][0].groupId).to.eql(undefined);
});
it('cancels a group subscription', async () => {
user = await generateUser({
'profile.name': 'sender',
'purchased.plan.customerId': 'customer-id',
'purchased.plan.planId': 'basic_3mo',
'purchased.plan.lastBillingDate': new Date(),
balance: 2,
});
group = await generateGroup(user, {
name: 'test group',
type: 'guild',
privacy: 'public',
'purchased.plan.customerId': 'customer-id',
'purchased.plan.planId': 'basic_3mo',
'purchased.plan.lastBillingDate': new Date(),
});
await user.get(`${endpoint}&groupId=${group._id}`);
expect(stripeCancelSubscriptionStub).to.be.calledOnce;
expect(stripeCancelSubscriptionStub.args[0][0].user._id).to.eql(user._id);
expect(stripeCancelSubscriptionStub.args[0][0].groupId).to.eql(group._id);
});
});
});
@@ -0,0 +1,75 @@
import {
generateUser,
generateGroup,
} from '../../../../../helpers/api-integration/v3';
import stripePayments from '../../../../../../website/server/libs/stripePayments';
describe('payments - stripe - #checkout', () => {
let endpoint = '/stripe/checkout';
let user, group;
beforeEach(async () => {
user = await generateUser();
});
it('verifies credentials', async () => {
await expect(user.post(endpoint, {id: 123})).to.eventually.be.rejected.and.eql({
code: 401,
error: 'Error',
message: 'Invalid API Key provided: ****************************1111',
});
});
describe('success', () => {
let stripeCheckoutSubscriptionStub;
beforeEach(async () => {
stripeCheckoutSubscriptionStub = sinon.stub(stripePayments, 'checkout').returnsPromise().resolves({});
});
afterEach(() => {
stripePayments.checkout.restore();
});
it('cancels a user subscription', async () => {
user = await generateUser({
'profile.name': 'sender',
'purchased.plan.customerId': 'customer-id',
'purchased.plan.planId': 'basic_3mo',
'purchased.plan.lastBillingDate': new Date(),
balance: 2,
});
await user.post(endpoint);
expect(stripeCheckoutSubscriptionStub).to.be.calledOnce;
expect(stripeCheckoutSubscriptionStub.args[0][0].user._id).to.eql(user._id);
expect(stripeCheckoutSubscriptionStub.args[0][0].groupId).to.eql(undefined);
});
it('cancels a group subscription', async () => {
user = await generateUser({
'profile.name': 'sender',
'purchased.plan.customerId': 'customer-id',
'purchased.plan.planId': 'basic_3mo',
'purchased.plan.lastBillingDate': new Date(),
balance: 2,
});
group = await generateGroup(user, {
name: 'test group',
type: 'guild',
privacy: 'public',
'purchased.plan.customerId': 'customer-id',
'purchased.plan.planId': 'basic_3mo',
'purchased.plan.lastBillingDate': new Date(),
});
await user.post(`${endpoint}?groupId=${group._id}`);
expect(stripeCheckoutSubscriptionStub).to.be.calledOnce;
expect(stripeCheckoutSubscriptionStub.args[0][0].user._id).to.eql(user._id);
expect(stripeCheckoutSubscriptionStub.args[0][0].groupId).to.eql(group._id);
});
});
});
@@ -0,0 +1,78 @@
import {
generateUser,
generateGroup,
translate as t,
} from '../../../../../helpers/api-integration/v3';
import stripePayments from '../../../../../../website/server/libs/stripePayments';
describe('payments - stripe - #subscribeEdit', () => {
let endpoint = '/stripe/subscribe/edit';
let user, group;
beforeEach(async () => {
user = await generateUser();
});
it('verifies credentials', async () => {
await expect(user.post(endpoint)).to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: t('missingSubscription'),
});
});
describe('success', () => {
let stripeEditSubscriptionStub;
beforeEach(async () => {
stripeEditSubscriptionStub = sinon.stub(stripePayments, 'editSubscription').returnsPromise().resolves({});
});
afterEach(() => {
stripePayments.editSubscription.restore();
});
it('cancels a user subscription', async () => {
user = await generateUser({
'profile.name': 'sender',
'purchased.plan.customerId': 'customer-id',
'purchased.plan.planId': 'basic_3mo',
'purchased.plan.lastBillingDate': new Date(),
balance: 2,
});
await user.post(endpoint);
expect(stripeEditSubscriptionStub).to.be.calledOnce;
expect(stripeEditSubscriptionStub.args[0][0].user._id).to.eql(user._id);
expect(stripeEditSubscriptionStub.args[0][0].groupId).to.eql(undefined);
});
it('cancels a group subscription', async () => {
user = await generateUser({
'profile.name': 'sender',
'purchased.plan.customerId': 'customer-id',
'purchased.plan.planId': 'basic_3mo',
'purchased.plan.lastBillingDate': new Date(),
balance: 2,
});
group = await generateGroup(user, {
name: 'test group',
type: 'guild',
privacy: 'public',
'purchased.plan.customerId': 'customer-id',
'purchased.plan.planId': 'basic_3mo',
'purchased.plan.lastBillingDate': new Date(),
});
await user.post(endpoint, {
groupId: group._id,
});
expect(stripeEditSubscriptionStub).to.be.calledOnce;
expect(stripeEditSubscriptionStub.args[0][0].user._id).to.eql(user._id);
expect(stripeEditSubscriptionStub.args[0][0].groupId).to.eql(group._id);
});
});
});
@@ -148,5 +148,20 @@ describe('POST /groups/:groupId/quests/accept', () => {
expect(rejectingMember.party.quest.key).to.not.exist;
expect(rejectingMember.party.quest.completed).to.not.exist;
});
it('begins the quest if accepting the last pending invite and verifies chat', async () => {
await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`);
await partyMembers[0].post(`/groups/${questingGroup._id}/quests/accept`);
// quest will start after everyone has accepted
await partyMembers[1].post(`/groups/${questingGroup._id}/quests/accept`);
await questingGroup.sync();
expect(questingGroup.chat[0].text).to.exist;
expect(questingGroup.chat[0]._meta).to.exist;
expect(questingGroup.chat[0]._meta).to.have.all.keys(['participatingMembers']);
let returnedGroup = await leader.get(`/groups/${questingGroup._id}`);
expect(returnedGroup.chat[0]._meta).to.be.undefined;
});
});
});
@@ -231,5 +231,22 @@ describe('POST /groups/:groupId/quests/force-start', () => {
expect(questingGroup.quest.members[partyMembers[0]._id]).to.exist;
expect(questingGroup.quest.members[leader._id]).to.exist;
});
it('allows group leader to force start quest and verifies chat', async () => {
let questLeader = partyMembers[0];
await questLeader.update({[`items.quests.${PET_QUEST}`]: 1});
await questLeader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`);
await leader.post(`/groups/${questingGroup._id}/quests/force-start`);
await questingGroup.sync();
expect(questingGroup.chat[0].text).to.exist;
expect(questingGroup.chat[0]._meta).to.exist;
expect(questingGroup.chat[0]._meta).to.have.all.keys(['participatingMembers']);
let returnedGroup = await leader.get(`/groups/${questingGroup._id}`);
expect(returnedGroup.chat[0]._meta).to.be.undefined;
});
});
});
@@ -188,5 +188,25 @@ describe('POST /groups/:groupId/quests/invite/:questKey', () => {
expect(group.quest.active).to.eql(true);
});
it('starts quest automatically if user is in a solo party and verifies chat', async () => {
let leaderDetails = { balance: 10 };
leaderDetails[`items.quests.${PET_QUEST}`] = 1;
let { group, groupLeader } = await createAndPopulateGroup({
groupDetails: { type: 'party', privacy: 'private' },
leaderDetails,
});
await groupLeader.post(`/groups/${group._id}/quests/invite/${PET_QUEST}`);
await group.sync();
expect(group.chat[0].text).to.exist;
expect(group.chat[0]._meta).to.exist;
expect(group.chat[0]._meta).to.have.all.keys(['participatingMembers']);
let returnedGroup = await groupLeader.get(`/groups/${group._id}`);
expect(returnedGroup.chat[0]._meta).to.be.undefined;
});
});
});
@@ -4,6 +4,7 @@ import {
generateUser,
} from '../../../../helpers/api-v3-integration.helper';
import { v4 as generateUUID } from 'uuid';
import { model as Group } from '../../../../../website/server/models/group';
describe('POST /groups/:groupId/quests/abort', () => {
let questingGroup;
@@ -89,6 +90,8 @@ describe('POST /groups/:groupId/quests/abort', () => {
await partyMembers[0].post(`/groups/${questingGroup._id}/quests/accept`);
await partyMembers[1].post(`/groups/${questingGroup._id}/quests/accept`);
let stub = sandbox.stub(Group.prototype, 'sendChat');
let res = await leader.post(`/groups/${questingGroup._id}/quests/abort`);
await Promise.all([
leader.sync(),
@@ -123,5 +126,9 @@ describe('POST /groups/:groupId/quests/abort', () => {
},
members: {},
});
expect(Group.prototype.sendChat).to.be.calledOnce;
expect(Group.prototype.sendChat).to.be.calledWithMatch(/aborted the party quest Wail of the Whale.`/);
stub.restore();
});
});
@@ -180,5 +180,19 @@ describe('POST /groups/:groupId/quests/reject', () => {
expect(rejectingMember.party.quest.key).to.not.exist;
expect(rejectingMember.party.quest.completed).to.not.exist;
});
it('starts the quest when the last user reject and verifies chat', async () => {
await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`);
await partyMembers[0].post(`/groups/${questingGroup._id}/quests/accept`);
await partyMembers[1].post(`/groups/${questingGroup._id}/quests/reject`);
await questingGroup.sync();
expect(questingGroup.chat[0].text).to.exist;
expect(questingGroup.chat[0]._meta).to.exist;
expect(questingGroup.chat[0]._meta).to.have.all.keys(['participatingMembers']);
let returnedGroup = await leader.get(`/groups/${questingGroup._id}`);
expect(returnedGroup.chat[0]._meta).to.be.undefined;
});
});
});
@@ -15,7 +15,7 @@ describe('GET /shops/seasonal', () => {
expect(shop.identifier).to.equal('seasonalShop');
expect(shop.text).to.eql(t('seasonalShop'));
expect(shop.notes).to.eql(t('seasonalShopFallText'));
expect(shop.notes).to.be.a('string');
expect(shop.imageName).to.be.a('string');
expect(shop.categories).to.be.an('array');
});
@@ -115,7 +115,7 @@ describe('GET /tasks/user', () => {
for (let i = 0; i < numberOfTodos; i++) {
let id = todos[i]._id;
await user.post(`/tasks/${id}/score/up`); // eslint-disable-line babel/no-await-in-loop
await user.post(`/tasks/${id}/score/up`); // eslint-disable-line no-await-in-loop
}
await user.sync();
@@ -5,7 +5,7 @@ import {
} from '../../../../helpers/api-integration/v3';
describe('POST /tasks/clearCompletedTodos', () => {
it('deletes all completed todos except the ones from a challenge', async () => {
it('deletes all completed todos except the ones from a challenge and group', async () => {
let user = await generateUser({balance: 1});
let guild = await generateGroup(user);
let challenge = await generateChallenge(user, guild);
@@ -24,12 +24,18 @@ describe('POST /tasks/clearCompletedTodos', () => {
type: 'todo',
});
let groupTask = await user.post(`/tasks/group/${guild._id}`, {
text: 'todo 7',
type: 'todo',
});
await user.post(`/tasks/${groupTask._id}/assign/${user._id}`);
let tasks = await user.get('/tasks/user?type=todos');
expect(tasks.length).to.equal(initialTodoCount + 6);
expect(tasks.length).to.equal(initialTodoCount + 7);
for (let task of tasks) {
if (['todo 2', 'todo 3', 'todo 6'].indexOf(task.text) !== -1) {
await user.post(`/tasks/${task._id}/score/up`); // eslint-disable-line babel/no-await-in-loop
await user.post(`/tasks/${task._id}/score/up`); // eslint-disable-line no-await-in-loop
}
}
@@ -37,7 +43,7 @@ describe('POST /tasks/clearCompletedTodos', () => {
let completedTodos = await user.get('/tasks/user?type=completedTodos');
let todos = await user.get('/tasks/user?type=todos');
let allTodos = todos.concat(completedTodos);
expect(allTodos.length).to.equal(initialTodoCount + 4); // + 6 - 3 completed (but one is from challenge)
expect(allTodos[allTodos.length - 1].text).to.equal('todo 6');
expect(allTodos.length).to.equal(initialTodoCount + 5); // + 7 - 3 completed (but one is from challenge)
expect(allTodos[allTodos.length - 1].text).to.equal('todo 6'); // last completed todo
});
});
@@ -74,6 +74,7 @@ describe('PUT /tasks/:id', () => {
checklist: [
{text: 123, completed: false},
],
collapseChecklist: false,
});
await sleep(2);
@@ -111,6 +112,7 @@ describe('PUT /tasks/:id', () => {
{text: 123, completed: false},
{text: 456, completed: true},
],
collapseChecklist: true,
notes: 'new notes',
attribute: 'per',
tags: [challengeUserTaskId],
@@ -143,6 +145,8 @@ describe('PUT /tasks/:id', () => {
expect(savedChallengeUserTask.streak).to.equal(25);
expect(savedChallengeUserTask.reminders.length).to.equal(2);
expect(savedChallengeUserTask.checklist.length).to.equal(2);
expect(savedChallengeUserTask.alias).to.equal('a-short-task-name');
expect(savedChallengeUserTask.collapseChecklist).to.equal(true);
});
});
@@ -5,12 +5,17 @@ import {
translate as t,
} from '../../../../../helpers/api-v3-integration.helper';
import { v4 as generateUUID } from 'uuid';
import { find } from 'lodash';
describe('POST /tasks/challenge/:challengeId', () => {
let user;
let guild;
let challenge;
function findUserChallengeTask (memberTask) {
return memberTask.challenge.id === challenge._id;
}
beforeEach(async () => {
user = await generateUser({balance: 1});
guild = await generateGroup(user);
@@ -88,6 +93,9 @@ describe('POST /tasks/challenge/:challengeId', () => {
});
let challengeWithTask = await user.get(`/challenges/${challenge._id}`);
let memberTasks = await user.get('/tasks/user');
let userChallengeTask = find(memberTasks, findUserChallengeTask);
expect(challengeWithTask.tasksOrder.habits.indexOf(task._id)).to.be.above(-1);
expect(task.challenge.id).to.equal(challenge._id);
expect(task.text).to.eql('test habit');
@@ -95,6 +103,8 @@ describe('POST /tasks/challenge/:challengeId', () => {
expect(task.type).to.eql('habit');
expect(task.up).to.eql(false);
expect(task.down).to.eql(true);
expect(userChallengeTask.notes).to.eql(task.notes);
});
it('creates a todo', async () => {
@@ -105,11 +115,16 @@ describe('POST /tasks/challenge/:challengeId', () => {
});
let challengeWithTask = await user.get(`/challenges/${challenge._id}`);
let memberTasks = await user.get('/tasks/user');
let userChallengeTask = find(memberTasks, findUserChallengeTask);
expect(challengeWithTask.tasksOrder.todos.indexOf(task._id)).to.be.above(-1);
expect(task.challenge.id).to.equal(challenge._id);
expect(task.text).to.eql('test todo');
expect(task.notes).to.eql('1976');
expect(task.type).to.eql('todo');
expect(userChallengeTask.notes).to.eql(task.notes);
});
it('creates a daily', async () => {
@@ -124,6 +139,9 @@ describe('POST /tasks/challenge/:challengeId', () => {
});
let challengeWithTask = await user.get(`/challenges/${challenge._id}`);
let memberTasks = await user.get('/tasks/user');
let userChallengeTask = find(memberTasks, findUserChallengeTask);
expect(challengeWithTask.tasksOrder.dailys.indexOf(task._id)).to.be.above(-1);
expect(task.challenge.id).to.equal(challenge._id);
expect(task.text).to.eql('test daily');
@@ -132,5 +150,7 @@ describe('POST /tasks/challenge/:challengeId', () => {
expect(task.frequency).to.eql('daily');
expect(task.everyX).to.eql(5);
expect(new Date(task.startDate)).to.eql(now);
expect(userChallengeTask.notes).to.eql(task.notes);
});
});
@@ -69,4 +69,48 @@ describe('DELETE /tasks/:id', () => {
expect(syncedTask.group.broken).to.equal('TASK_DELETED');
expect(member2SyncedTask.group.broken).to.equal('TASK_DELETED');
});
it('prevents a user from deleting a task they are assigned to', async () => {
let memberTasks = await member.get('/tasks/user');
let syncedTask = find(memberTasks, findAssignedTask);
await expect(member.del(`/tasks/${syncedTask._id}`))
.to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: t('cantDeleteAssignedGroupTasks'),
});
});
it('allows a user to delete a broken task', async () => {
let memberTasks = await member.get('/tasks/user');
let syncedTask = find(memberTasks, findAssignedTask);
await user.del(`/tasks/${task._id}`);
await member.del(`/tasks/${syncedTask._id}`);
await expect(member.get(`/tasks/${syncedTask._id}`))
.to.eventually.be.rejected.and.eql({
code: 404,
error: 'NotFound',
message: 'Task not found.',
});
});
it('allows a user to delete a task after leaving a group', async () => {
let memberTasks = await member.get('/tasks/user');
let syncedTask = find(memberTasks, findAssignedTask);
await member.post(`/groups/${guild._id}/leave`);
await member.del(`/tasks/${syncedTask._id}`);
await expect(member.get(`/tasks/${syncedTask._id}`))
.to.eventually.be.rejected.and.eql({
code: 404,
error: 'NotFound',
message: 'Task not found.',
});
});
});
@@ -0,0 +1,58 @@
import {
createAndPopulateGroup,
translate as t,
} from '../../../../../helpers/api-integration/v3';
import { find } from 'lodash';
describe('GET /approvals/group/:groupId', () => {
let user, guild, member, task, syncedTask;
function findAssignedTask (memberTask) {
return memberTask.group.id === guild._id;
}
beforeEach(async () => {
let {group, members, groupLeader} = await createAndPopulateGroup({
groupDetails: {
name: 'Test Guild',
type: 'guild',
},
members: 1,
});
guild = group;
user = groupLeader;
member = members[0];
task = await user.post(`/tasks/group/${guild._id}`, {
text: 'test todo',
type: 'todo',
requiresApproval: true,
});
await user.post(`/tasks/${task._id}/assign/${member._id}`);
let memberTasks = await member.get('/tasks/user');
syncedTask = find(memberTasks, findAssignedTask);
try {
await member.post(`/tasks/${syncedTask._id}/score/up`);
} catch (e) {
// eslint-disable-next-line no-empty
}
});
it('errors when user is not the group leader', async () => {
await expect(member.get(`/approvals/group/${guild._id}`))
.to.eventually.be.rejected.and.to.eql({
code: 401,
error: 'NotAuthorized',
message: t('onlyGroupLeaderCanEditTasks'),
});
});
it('gets a list of task that need approval', async () => {
let approvals = await user.get(`/approvals/group/${guild._id}`);
expect(approvals[0]._id).to.equal(syncedTask._id);
});
});
@@ -0,0 +1,72 @@
import {
createAndPopulateGroup,
translate as t,
} from '../../../../../helpers/api-integration/v3';
import { find } from 'lodash';
describe('POST /tasks/:id/approve/:userId', () => {
let user, guild, member, task;
function findAssignedTask (memberTask) {
return memberTask.group.id === guild._id;
}
beforeEach(async () => {
let {group, members, groupLeader} = await createAndPopulateGroup({
groupDetails: {
name: 'Test Guild',
type: 'guild',
},
members: 1,
});
guild = group;
user = groupLeader;
member = members[0];
task = await user.post(`/tasks/group/${guild._id}`, {
text: 'test todo',
type: 'todo',
requiresApproval: true,
});
});
it('errors when user is not assigned', async () => {
await expect(user.post(`/tasks/${task._id}/approve/${member._id}`))
.to.eventually.be.rejected.and.to.eql({
code: 404,
error: 'NotFound',
message: t('taskNotFound'),
});
});
it('errors when user is not the group leader', async () => {
await user.post(`/tasks/${task._id}/assign/${member._id}`);
await expect(member.post(`/tasks/${task._id}/approve/${member._id}`))
.to.eventually.be.rejected.and.to.eql({
code: 401,
error: 'NotAuthorized',
message: t('onlyGroupLeaderCanEditTasks'),
});
});
it('approves an assigned user', async () => {
await user.post(`/tasks/${task._id}/assign/${member._id}`);
await user.post(`/tasks/${task._id}/approve/${member._id}`);
let memberTasks = await member.get('/tasks/user');
let syncedTask = find(memberTasks, findAssignedTask);
await member.sync();
expect(member.notifications.length).to.equal(2);
expect(member.notifications[0].type).to.equal('GROUP_TASK_APPROVED');
expect(member.notifications[0].data.message).to.equal(t('yourTaskHasBeenApproved', {taskText: task.text}));
expect(member.notifications[1].type).to.equal('SCORED_TASK');
expect(member.notifications[1].data.message).to.equal(t('yourTaskHasBeenApproved', {taskText: task.text}));
expect(syncedTask.group.approval.approved).to.be.true;
expect(syncedTask.group.approval.approvingUser).to.equal(user._id);
expect(syncedTask.group.approval.dateApproved).to.be.a('string'); // date gets converted to a string as json doesn't have a Date type
});
});
@@ -0,0 +1,97 @@
import {
createAndPopulateGroup,
translate as t,
} from '../../../../../helpers/api-integration/v3';
import { find } from 'lodash';
describe('POST /tasks/:id/score/:direction', () => {
let user, guild, member, task;
function findAssignedTask (memberTask) {
return memberTask.group.id === guild._id;
}
beforeEach(async () => {
let {group, members, groupLeader} = await createAndPopulateGroup({
groupDetails: {
name: 'Test Guild',
type: 'guild',
},
members: 1,
});
guild = group;
user = groupLeader;
member = members[0];
task = await user.post(`/tasks/group/${guild._id}`, {
text: 'test todo',
type: 'todo',
requiresApproval: true,
});
await user.post(`/tasks/${task._id}/assign/${member._id}`);
});
it('prevents user from scoring a task that needs to be approved', async () => {
await user.update({
'preferences.language': 'cs',
});
let memberTasks = await member.get('/tasks/user');
let syncedTask = find(memberTasks, findAssignedTask);
await expect(member.post(`/tasks/${syncedTask._id}/score/up`))
.to.eventually.be.rejected.and.to.eql({
code: 401,
error: 'NotAuthorized',
message: t('taskApprovalHasBeenRequested'),
});
let updatedTask = await member.get(`/tasks/${syncedTask._id}`);
await user.sync();
expect(user.notifications.length).to.equal(1);
expect(user.notifications[0].type).to.equal('GROUP_TASK_APPROVAL');
expect(user.notifications[0].data.message).to.equal(t('userHasRequestedTaskApproval', {
user: member.auth.local.username,
taskName: updatedTask.text,
}, 'cs')); // This test only works if we have the notification translated
expect(user.notifications[0].data.groupId).to.equal(guild._id);
expect(updatedTask.group.approval.requested).to.equal(true);
expect(updatedTask.group.approval.requestedDate).to.be.a('string'); // date gets converted to a string as json doesn't have a Date type
});
it('errors when approval has already been requested', async () => {
let memberTasks = await member.get('/tasks/user');
let syncedTask = find(memberTasks, findAssignedTask);
await expect(member.post(`/tasks/${syncedTask._id}/score/up`))
.to.eventually.be.rejected.and.to.eql({
code: 401,
error: 'NotAuthorized',
message: t('taskApprovalHasBeenRequested'),
});
await expect(member.post(`/tasks/${syncedTask._id}/score/up`))
.to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: t('taskRequiresApproval'),
});
});
it('allows a user to score an apporoved task', async () => {
let memberTasks = await member.get('/tasks/user');
let syncedTask = find(memberTasks, findAssignedTask);
await user.post(`/tasks/${task._id}/approve/${member._id}`);
await member.post(`/tasks/${syncedTask._id}/score/up`);
let updatedTask = await member.get(`/tasks/${syncedTask._id}`);
expect(updatedTask.completed).to.equal(true);
expect(updatedTask.dateCompleted).to.be.a('string'); // date gets converted to a string as json doesn't have a Date type
});
});
@@ -82,7 +82,7 @@ describe('POST /tasks/:taskId', () => {
});
});
it('allows user to assign themselves', async () => {
it('allows user to assign themselves (claim)', async () => {
await member.post(`/tasks/${task._id}/assign/${member._id}`);
let groupTask = await user.get(`/tasks/group/${guild._id}`);
@@ -93,6 +93,15 @@ describe('POST /tasks/:taskId', () => {
expect(syncedTask).to.exist;
});
it('sends a message to the group when a user claims a task', async () => {
await member.post(`/tasks/${task._id}/assign/${member._id}`);
let updateGroup = await user.get(`/groups/${guild._id}`);
expect(updateGroup.chat[0].text).to.equal(t('userIsClamingTask', {username: member.profile.name, task: task.text}));
expect(updateGroup.chat[0].uuid).to.equal('system');
});
it('assigns a task to a user', async () => {
await user.post(`/tasks/${task._id}/assign/${member._id}`);
@@ -0,0 +1,49 @@
import {
generateUser,
generateGroup,
} from '../../../../../helpers/api-v3-integration.helper';
describe('POST group-tasks/:taskId/move/to/:position', () => {
let user, guild;
beforeEach(async () => {
user = await generateUser({balance: 1});
guild = await generateGroup(user, {type: 'guild'});
});
it('can move task to new position', async () => {
let tasks = await user.post(`/tasks/group/${guild._id}`, [
{type: 'habit', text: 'habit 1'},
{type: 'habit', text: 'habit 2'},
{type: 'daily', text: 'daily 1'},
{type: 'habit', text: 'habit 3'},
{type: 'habit', text: 'habit 4'},
{type: 'todo', text: 'todo 1'},
{type: 'habit', text: 'habit 5'},
]);
let taskToMove = tasks[1];
expect(taskToMove.text).to.equal('habit 2');
let newOrder = await user.post(`/group-tasks/${tasks[1]._id}/move/to/3`);
expect(newOrder[3]).to.equal(taskToMove._id);
expect(newOrder.length).to.equal(5);
});
it('can push to bottom', async () => {
let tasks = await user.post(`/tasks/group/${guild._id}`, [
{type: 'habit', text: 'habit 1'},
{type: 'habit', text: 'habit 2'},
{type: 'daily', text: 'daily 1'},
{type: 'habit', text: 'habit 3'},
{type: 'habit', text: 'habit 4'},
{type: 'todo', text: 'todo 1'},
{type: 'habit', text: 'habit 5'},
]);
let taskToMove = tasks[1];
expect(taskToMove.text).to.equal('habit 2');
let newOrder = await user.post(`/group-tasks/${tasks[1]._id}/move/to/-1`);
expect(newOrder[4]).to.equal(taskToMove._id);
expect(newOrder.length).to.equal(5);
});
});
@@ -11,6 +11,10 @@ import {
map,
} from 'lodash';
import Bluebird from 'bluebird';
import {
sha1MakeSalt,
sha1Encrypt as sha1EncryptPassword,
} from '../../../../../website/server/libs/password';
describe('DELETE /user', () => {
let user;
@@ -67,6 +71,30 @@ describe('DELETE /user', () => {
await expect(checkExistence('users', user._id)).to.eventually.eql(false);
});
it('deletes the user with a legacy sha1 password', async () => {
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);
// delete the user
await user.del('/user', {
password: textPassword,
});
await expect(checkExistence('users', user._id)).to.eventually.eql(false);
});
context('last member of a party', () => {
let party;
@@ -23,6 +23,7 @@ describe('GET /user', () => {
let returnedUser = await user.get('/user');
expect(returnedUser.auth.local.hashed_password).to.not.exist;
expect(returnedUser.auth.local.passwordHashMethod).to.not.exist;
expect(returnedUser.auth.local.salt).to.not.exist;
expect(returnedUser.apiToken).to.not.exist;
});
@@ -62,15 +62,6 @@ describe('POST /user/buy/:key', () => {
await user.post(`/user/buy/${key}`);
await user.sync();
expect(user.items.gear.owned).to.eql({
armor_warrior_1: true,
eyewear_special_blackTopFrame: true,
eyewear_special_blueTopFrame: true,
eyewear_special_greenTopFrame: true,
eyewear_special_pinkTopFrame: true,
eyewear_special_redTopFrame: true,
eyewear_special_whiteTopFrame: true,
eyewear_special_yellowTopFrame: true,
});
expect(user.items.gear.owned.armor_warrior_1).to.eql(true);
});
});
@@ -31,15 +31,6 @@ describe('POST /user/buy-gear/:key', () => {
await user.post(`/user/buy-gear/${key}`);
await user.sync();
expect(user.items.gear.owned).to.eql({
armor_warrior_1: true,
eyewear_special_blackTopFrame: true,
eyewear_special_blueTopFrame: true,
eyewear_special_greenTopFrame: true,
eyewear_special_pinkTopFrame: true,
eyewear_special_redTopFrame: true,
eyewear_special_whiteTopFrame: true,
eyewear_special_yellowTopFrame: true,
});
expect(user.items.gear.owned.armor_warrior_1).to.eql(true);
});
});
@@ -2,11 +2,13 @@ import {
generateUser,
translate as t,
createAndPopulateGroup,
generateGroup,
generateChallenge,
sleep,
} from '../../../../helpers/api-integration/v3';
import { v4 as generateUUID } from 'uuid';
import { find } from 'lodash';
describe('POST /user/class/cast/:spellId', () => {
let user;
@@ -120,6 +122,31 @@ describe('POST /user/class/cast/:spellId', () => {
});
});
it('returns an error if a group task was targeted', async () => {
let {group, groupLeader} = await createAndPopulateGroup();
let groupTask = await groupLeader.post(`/tasks/group/${group._id}`, {
text: 'todo group',
type: 'todo',
});
await groupLeader.post(`/tasks/${groupTask._id}/assign/${groupLeader._id}`);
let memberTasks = await groupLeader.get('/tasks/user');
let syncedGroupTask = find(memberTasks, function findAssignedTask (memberTask) {
return memberTask.group.id === group._id;
});
await groupLeader.update({'stats.class': 'rogue', 'stats.lvl': 11});
await sleep(0.5);
await groupLeader.sync();
await expect(groupLeader.post(`/user/class/cast/pickPocket?targetId=${syncedGroupTask._id}`))
.to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('groupTasksNoCast'),
});
});
it('returns an error if targeted party member doesn\'t exist', async () => {
let {groupLeader} = await createAndPopulateGroup({
groupDetails: { type: 'party', privacy: 'private' },
@@ -160,6 +187,40 @@ describe('POST /user/class/cast/:spellId', () => {
expect(group.chat[0].uuid).to.equal('system');
});
it('searing brightness does not affect challenge or group tasks', async () => {
let guild = await generateGroup(user);
let challenge = await generateChallenge(user, guild);
await user.post(`/tasks/challenge/${challenge._id}`, {
text: 'test challenge habit',
type: 'habit',
});
let groupTask = await user.post(`/tasks/group/${guild._id}`, {
text: 'todo group',
type: 'todo',
});
await user.update({'stats.class': 'healer', 'stats.mp': 200, 'stats.lvl': 15});
await user.post(`/tasks/${groupTask._id}/assign/${user._id}`);
await user.post('/user/class/cast/brightness');
await user.sync();
let memberTasks = await user.get('/tasks/user');
let syncedGroupTask = find(memberTasks, function findAssignedTask (memberTask) {
return memberTask.group.id === guild._id;
});
let userChallengeTask = find(memberTasks, function findAssignedTask (memberTask) {
return memberTask.challenge.id === challenge._id;
});
expect(userChallengeTask).to.exist;
expect(syncedGroupTask).to.exist;
expect(userChallengeTask.value).to.equal(0);
expect(syncedGroupTask.value).to.equal(0);
});
// TODO find a way to have sinon working in integration tests
// it doesn't work when tests are running separately from server
it('passes correct target to spell when targetType === \'task\'');
@@ -20,6 +20,6 @@ describe('POST /user/purchase-hourglass/:type/:key', () => {
expect(response.message).to.eql(t('hourglassPurchase'));
expect(user.purchased.plan.consecutive.trinkets).to.eql(1);
expect(user.items.pets).to.eql({'MantisShrimp-Base': 5});
expect(user.items.pets['MantisShrimp-Base']).to.eql(5);
});
});

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