Compare commits

..

316 Commits

Author SHA1 Message Date
Sabe Jones
da796c305d 4.156.3 2020-09-11 12:56:06 -05:00
Matteo Pagliazzi
953c84260f fix(tasks and groupd): do not apply default due filter to challenges, fix edge case in group loading 2020-09-11 19:53:28 +02:00
Sabe Jones
be58c8193e 4.156.2 2020-09-10 15:14:14 -05:00
Sabe Jones
60ce16bf08 chore(news): bailey 2020-09-10 15:14:04 -05:00
Sabe Jones
83ec53c614 Merge branch 'develop' into release 2020-09-10 15:06:51 -05:00
Sabe Jones
5fa140376e fix(sprites): pixel scoot 2020-09-10 09:33:47 -05:00
Matteo Pagliazzi
79b218ecf0 Merge branch 'release' of github.com:HabitRPG/habitica into develop 2020-09-10 11:40:25 +02:00
Matteo Pagliazzi
e2442fe56d fix(test): tag.challenge is a boolean 2020-09-10 11:39:19 +02:00
Matteo Pagliazzi
d9e85b329f 4.156.1 2020-09-10 11:17:54 +02:00
Matteo Pagliazzi
05f22ababc fix(challenge tags); change type to Mixed in preparation for migration 2020-09-10 11:14:09 +02:00
Melior
b61162e475 Merge branch 'origin/develop' into Weblate. 2020-09-08 23:16:12 +02:00
Sabe Jones
d7cc317208 Merge branch 'release' into develop 2020-09-08 16:11:29 -05:00
Sabe Jones
9f988acb55 4.156.0 2020-09-08 16:10:36 -05:00
Sabe Jones
eec57cb9b8 chore(sprites): compile 2020-09-08 16:10:17 -05:00
Sabe Jones
f5eb868763 feat(content): Backgrounds and Armoire items 2020-09-08 16:10:01 -05:00
Matteo Pagliazzi
96d22e54f8 fix(task creation): correctly clone selected tags array, fixes #12447 2020-09-08 12:52:09 +02:00
Melior
03bf7b7d59 Translated using Weblate (Dutch)
Currently translated at 99.8% (2131 of 2135 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/nl/

Translated using Weblate (Czech)

Currently translated at 89.0% (1902 of 2135 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/cs/

Translated using Weblate (Dutch)

Currently translated at 99.7% (2129 of 2135 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/nl/

Translated using Weblate (Japanese)

Currently translated at 100.0% (93 of 93 strings)

Translation: Habitica/Achievements
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/ja/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (708 of 708 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/zh_Hans/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (361 of 361 strings)

Translation: Habitica/Content
Translate-URL: https://translate.habitica.com/projects/habitica/content/zh_Hans/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (252 of 252 strings)

Translation: Habitica/Subscriber
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/zh_Hans/

Translated using Weblate (Chinese (Simplified))

Currently translated at 99.4% (704 of 708 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/zh_Hans/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (93 of 93 strings)

Translation: Habitica/Achievements
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/zh_Hans/

Translated using Weblate (German)

Currently translated at 100.0% (56 of 56 strings)

Translation: Habitica/Faq
Translate-URL: https://translate.habitica.com/projects/habitica/faq/de/

Translated using Weblate (English (Pirate))

Currently translated at 96.4% (483 of 501 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/en@pirate/

Translated using Weblate (Italian)

Currently translated at 100.0% (2135 of 2135 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/it/

Translated using Weblate (English (Pirate))

Currently translated at 99.6% (297 of 298 strings)

Translation: Habitica/Generic
Translate-URL: https://translate.habitica.com/projects/habitica/generic/en@pirate/

Translated using Weblate (Italian)

Currently translated at 100.0% (708 of 708 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/it/

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (252 of 252 strings)

Translation: Habitica/Subscriber
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/vi/

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (501 of 501 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/vi/

Translated using Weblate (German)

Currently translated at 100.0% (2135 of 2135 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/de/

Translated using Weblate (German)

Currently translated at 94.6% (53 of 56 strings)

Translation: Habitica/Faq
Translate-URL: https://translate.habitica.com/projects/habitica/faq/de/

Translated using Weblate (German)

Currently translated at 100.0% (550 of 550 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/de/

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (93 of 93 strings)

Translation: Habitica/Achievements
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/vi/

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (361 of 361 strings)

Translation: Habitica/Content
Translate-URL: https://translate.habitica.com/projects/habitica/content/vi/

Translated using Weblate (Spanish)

Currently translated at 93.9% (665 of 708 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/es/

Translated using Weblate (Spanish)

Currently translated at 100.0% (93 of 93 strings)

Translation: Habitica/Achievements
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/es/

Translated using Weblate (Spanish)

Currently translated at 100.0% (93 of 93 strings)

Translation: Habitica/Achievements
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/es/

Translated using Weblate (Spanish)

Currently translated at 100.0% (212 of 212 strings)

Translation: Habitica/Settings
Translate-URL: https://translate.habitica.com/projects/habitica/settings/es/

Translated using Weblate (Italian)

Currently translated at 100.0% (252 of 252 strings)

Translation: Habitica/Subscriber
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (501 of 501 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (708 of 708 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (2135 of 2135 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (56 of 56 strings)

Translation: Habitica/Faq
Translate-URL: https://translate.habitica.com/projects/habitica/faq/it/

Translated using Weblate (Italian)

Currently translated at 99.5% (705 of 708 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (361 of 361 strings)

Translation: Habitica/Content
Translate-URL: https://translate.habitica.com/projects/habitica/content/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (93 of 93 strings)

Translation: Habitica/Achievements
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/it/

Translated using Weblate (Spanish (Latin America))

Currently translated at 100.0% (146 of 146 strings)

Translation: Habitica/Pets
Translate-URL: https://translate.habitica.com/projects/habitica/pets/es_419/

Translated using Weblate (Spanish (Latin America))

Currently translated at 98.6% (144 of 146 strings)

Translation: Habitica/Pets
Translate-URL: https://translate.habitica.com/projects/habitica/pets/es_419/

Translated using Weblate (Spanish (Latin America))

Currently translated at 97.1% (206 of 212 strings)

Translation: Habitica/Settings
Translate-URL: https://translate.habitica.com/projects/habitica/settings/es_419/

Translated using Weblate (French)

Currently translated at 100.0% (252 of 252 strings)

Translation: Habitica/Subscriber
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/fr/

Translated using Weblate (German)

Currently translated at 100.0% (252 of 252 strings)

Translation: Habitica/Subscriber
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/de/

Translated using Weblate (Spanish (Latin America))

Currently translated at 100.0% (57 of 57 strings)

Translation: Habitica/Spells
Translate-URL: https://translate.habitica.com/projects/habitica/spells/es_419/

Translated using Weblate (Spanish (Latin America))

Currently translated at 93.8% (199 of 212 strings)

Translation: Habitica/Settings
Translate-URL: https://translate.habitica.com/projects/habitica/settings/es_419/

Translated using Weblate (German)

Currently translated at 100.0% (501 of 501 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/de/

Translated using Weblate (French)

Currently translated at 100.0% (2135 of 2135 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/fr/

Translated using Weblate (German)

Currently translated at 99.8% (2131 of 2135 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/de/

Translated using Weblate (French)

Currently translated at 100.0% (56 of 56 strings)

Translation: Habitica/Faq
Translate-URL: https://translate.habitica.com/projects/habitica/faq/fr/

Translated using Weblate (Russian)

Currently translated at 98.7% (699 of 708 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/ru/

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (708 of 708 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/pt_BR/

Translated using Weblate (French)

Currently translated at 100.0% (702 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/fr/

Translated using Weblate (French)

Currently translated at 100.0% (702 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/fr/

Translated using Weblate (German)

Currently translated at 99.5% (705 of 708 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/de/

Translated using Weblate (Russian)

Currently translated at 99.7% (360 of 361 strings)

Translation: Habitica/Content
Translate-URL: https://translate.habitica.com/projects/habitica/content/ru/

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (361 of 361 strings)

Translation: Habitica/Content
Translate-URL: https://translate.habitica.com/projects/habitica/content/pt_BR/

Translated using Weblate (French)

Currently translated at 100.0% (361 of 361 strings)

Translation: Habitica/Content
Translate-URL: https://translate.habitica.com/projects/habitica/content/fr/

Translated using Weblate (German)

Currently translated at 100.0% (361 of 361 strings)

Translation: Habitica/Content
Translate-URL: https://translate.habitica.com/projects/habitica/content/de/

Translated using Weblate (Russian)

Currently translated at 100.0% (93 of 93 strings)

Translation: Habitica/Achievements
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/ru/

Translated using Weblate (French)

Currently translated at 100.0% (93 of 93 strings)

Translation: Habitica/Achievements
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/fr/

Translated using Weblate (German)

Currently translated at 100.0% (93 of 93 strings)

Translation: Habitica/Achievements
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/de/

Translated using Weblate (French)

Currently translated at 100.0% (702 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/fr/

Translated using Weblate (French)

Currently translated at 100.0% (702 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/fr/

Translated using Weblate (French)

Currently translated at 100.0% (702 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/fr/

Translated using Weblate (French)

Currently translated at 100.0% (702 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/fr/

Translated using Weblate (French)

Currently translated at 100.0% (702 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/fr/

Translated using Weblate (French)

Currently translated at 100.0% (702 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/fr/

Translated using Weblate (French)

Currently translated at 100.0% (702 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/fr/

Translated using Weblate (French)

Currently translated at 100.0% (702 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/fr/

Translated using Weblate (French)

Currently translated at 100.0% (702 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/fr/

Translated using Weblate (French)

Currently translated at 100.0% (702 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/fr/
2020-09-08 10:44:22 +02:00
Matteo Pagliazzi
78016c0aeb fix(modals): apply base styles everywhere, fixes #12524 2020-09-07 21:24:58 +02:00
tsukimi2
b0786647ed Bugfix challenge tags to normal tags after a challenge has ended/deleted (#12341)
* Fix bug in challenge tags not converted to normal tags after challenge ended/deleted

* Added test cases to test bug fix

* Set tag.challenge from String to Boolean in tag model schema

* Update existing test with tag challenge set to boolean instead of string

* Added migration file for converting tag challenge field from string to bool

* Implement suggestions from ilnt

* Use mongoose instead of Mock in migration

* Change from update to bulkwrite

* update users individually

Co-authored-by: Matteo Pagliazzi <matteopagliazzi@gmail.com>
2020-09-07 16:48:22 +02:00
Kirsty
1b25d30ac6 Correctly filter tasks in challenges and remove filters where unhelpful (#12522)
* Correctly filter tasks in challenges and remove filters where unhelpful

* update isDue property on the client as soon as challenges are created or edited

* change method of update for task on receiving data

* try different assign method

* fix lint

* fix issue with data reactivity

Co-authored-by: Matteo Pagliazzi <matteopagliazzi@gmail.com>
2020-09-07 16:11:28 +02:00
Matteo Pagliazzi
362677acb8 fix(checklists): correctly hide original input in disabled state, fixes #12528 2020-09-07 15:42:04 +02:00
Kirsty
d37c156fa0 Filter challenge by owned (#12527)
* when filtering by owned challenges, challenges that a user owns but has not joined will be included

* add tests for filtering challenges by owned

* fix lint
2020-09-07 15:30:01 +02:00
dependabot-preview[bot]
e5ccb634e0 build(deps): bump @babel/core from 7.11.4 to 7.11.6 (#12536)
Bumps [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core) from 7.11.4 to 7.11.6.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.11.6/packages/babel-core)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-09-07 11:59:03 +02:00
dependabot-preview[bot]
cea6cbad50 build(deps): bump image-size from 0.8.3 to 0.9.1 (#12538)
Bumps [image-size](https://github.com/image-size/image-size) from 0.8.3 to 0.9.1.
- [Release notes](https://github.com/image-size/image-size/releases)
- [Commits](https://github.com/image-size/image-size/compare/v0.8.3...v0.9.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-09-07 11:57:00 +02:00
dependabot-preview[bot]
68584e25c1 build(deps): bump @babel/preset-env from 7.11.0 to 7.11.5 (#12534)
Bumps [@babel/preset-env](https://github.com/babel/babel/tree/HEAD/packages/babel-preset-env) from 7.11.0 to 7.11.5.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.11.5/packages/babel-preset-env)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-09-07 11:55:31 +02:00
dependabot-preview[bot]
fbab46eab0 build(deps-dev): bump monk from 7.3.1 to 7.3.2 (#12533)
Bumps [monk](https://github.com/Automattic/monk) from 7.3.1 to 7.3.2.
- [Release notes](https://github.com/Automattic/monk/releases)
- [Changelog](https://github.com/Automattic/monk/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Automattic/monk/compare/v7.3.1...v7.3.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-09-07 11:55:25 +02:00
dependabot-preview[bot]
2740c02f35 build(deps): bump mongoose from 5.10.2 to 5.10.3 (#12532)
Bumps [mongoose](https://github.com/Automattic/mongoose) from 5.10.2 to 5.10.3.
- [Release notes](https://github.com/Automattic/mongoose/releases)
- [Changelog](https://github.com/Automattic/mongoose/blob/master/History.md)
- [Commits](https://github.com/Automattic/mongoose/compare/5.10.2...5.10.3)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-09-07 11:55:16 +02:00
dependabot-preview[bot]
ffb51fc18b build(deps): bump habitica-markdown from 2.0.2 to 3.0.0 (#12537)
Bumps [habitica-markdown](https://github.com/HabitRPG/habitica-markdown) from 2.0.2 to 3.0.0.
- [Release notes](https://github.com/HabitRPG/habitica-markdown/releases)
- [Commits](https://github.com/HabitRPG/habitica-markdown/compare/v2.0.2...v3.0.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-09-07 11:53:58 +02:00
dependabot-preview[bot]
d19fb4f489 build(deps): bump got from 11.5.2 to 11.6.0 (#12539)
Bumps [got](https://github.com/sindresorhus/got) from 11.5.2 to 11.6.0.
- [Release notes](https://github.com/sindresorhus/got/releases)
- [Commits](https://github.com/sindresorhus/got/compare/v11.5.2...v11.6.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-09-07 11:53:42 +02:00
dependabot-preview[bot]
49bfe386a6 build(deps): [security] bump serialize-javascript in /website/client (#12542)
Bumps [serialize-javascript](https://github.com/yahoo/serialize-javascript) from 3.0.0 to 3.1.0. **This update includes a security fix.**
- [Release notes](https://github.com/yahoo/serialize-javascript/releases)
- [Commits](https://github.com/yahoo/serialize-javascript/compare/v3.0.0...v3.1.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-09-07 11:53:32 +02:00
dependabot-preview[bot]
93ee579bbf build(deps): bump @babel/register from 7.10.5 to 7.11.5 (#12541)
Bumps [@babel/register](https://github.com/babel/babel/tree/HEAD/packages/babel-register) from 7.10.5 to 7.11.5.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.11.5/packages/babel-register)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-09-07 11:52:48 +02:00
dependabot-preview[bot]
f9ed36a9f2 build(deps): bump habitica-markdown in /website/client (#12545)
Bumps [habitica-markdown](https://github.com/HabitRPG/habitica-markdown) from 2.0.2 to 3.0.0.
- [Release notes](https://github.com/HabitRPG/habitica-markdown/releases)
- [Commits](https://github.com/HabitRPG/habitica-markdown/compare/v2.0.2...v3.0.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-09-07 11:52:29 +02:00
Matteo Pagliazzi
e965f6a28f Merge branch 'release' into develop 2020-09-05 13:10:57 +02:00
Sabe Jones
d98932e183 4.155.2 2020-09-04 17:30:54 -05:00
Sabe Jones
a91c2f7c7c fix(settings): revert axios version due to account delete issue 2020-09-04 17:30:46 -05:00
Mike-Antonacci
5122d137b0 Change spellRogueStealthMaxedOut and spellAlreadyCast to a clearer wo… (#12497)
* Change spellRogueStealthMaxedOut and spellAlreadyCast to a clearer wording

* Restore package-lock.json and Delete string spellRogueStealthMaxedOut

* remove unused string, restore package-lock.json changes

* restore client package-lock.json

* Change spellRogueStealthMaxedOut and spellAlreadyCast to a clearer wording

* Restore package-lock.json and Delete string spellRogueStealthMaxedOut

* remove unused string, restore package-lock.json changes

* restore client package-lock.json

Co-authored-by: Matteo Pagliazzi <matteopagliazzi@gmail.com>
2020-09-04 12:21:50 +02:00
Sabe Jones
70d057e66c 4.155.1 2020-09-03 17:22:54 -05:00
Sabe Jones
b680b6026b fix(teams): don't adjust source task value for rewards Fixes #12523 2020-09-03 10:00:32 -05:00
Matteo Pagliazzi
0c3b16ca74 fix(task modal): make sure task is loaded when template is shown 2020-09-03 16:33:44 +02:00
Melior
7be039a35f Translated using Weblate (Portuguese (Brazil))
Currently translated at 99.7% (706 of 708 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/pt_BR/

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (252 of 252 strings)

Translation: Habitica/Subscriber
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/pt_BR/

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (501 of 501 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/pt_BR/

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (2135 of 2135 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/pt_BR/

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (56 of 56 strings)

Translation: Habitica/Faq
Translate-URL: https://translate.habitica.com/projects/habitica/faq/pt_BR/

Translated using Weblate (Portuguese (Brazil))

Currently translated at 99.2% (703 of 708 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/pt_BR/

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (93 of 93 strings)

Translation: Habitica/Achievements
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/pt_BR/

Merge branch 'origin/develop' into Weblate.

Translated using Weblate (Italian)

Currently translated at 100.0% (2131 of 2131 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/it/

Translated using Weblate (Dutch)

Currently translated at 99.8% (2127 of 2131 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/nl/

Translated using Weblate (French)

Currently translated at 100.0% (702 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/fr/

Translated using Weblate (French)

Currently translated at 100.0% (702 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/fr/

Translated using Weblate (French)

Currently translated at 100.0% (92 of 92 strings)

Translation: Habitica/Achievements
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/fr/

Translated using Weblate (Greek)

Currently translated at 73.2% (41 of 56 strings)

Translation: Habitica/Faq
Translate-URL: https://translate.habitica.com/projects/habitica/faq/el/

Translated using Weblate (Greek)

Currently translated at 100.0% (217 of 217 strings)

Translation: Habitica/Tasks
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/el/

Translated using Weblate (Greek)

Currently translated at 100.0% (6 of 6 strings)

Translation: Habitica/Inventory
Translate-URL: https://translate.habitica.com/projects/habitica/inventory/el/

Translated using Weblate (Greek)

Currently translated at 79.8% (1702 of 2131 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/el/

Translated using Weblate (Greek)

Currently translated at 100.0% (70 of 70 strings)

Translation: Habitica/Contrib
Translate-URL: https://translate.habitica.com/projects/habitica/contrib/el/

Translated using Weblate (Greek)

Currently translated at 100.0% (4 of 4 strings)

Translation: Habitica/Noscript
Translate-URL: https://translate.habitica.com/projects/habitica/noscript/el/

Translated using Weblate (Greek)

Currently translated at 100.0% (27 of 27 strings)

Translation: Habitica/Loginincentives
Translate-URL: https://translate.habitica.com/projects/habitica/loginincentives/el/

Translated using Weblate (Czech)

Currently translated at 88.9% (1895 of 2131 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/cs/

Translated using Weblate (French)

Currently translated at 100.0% (92 of 92 strings)

Translation: Habitica/Achievements
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/fr/

Translated using Weblate (French)

Currently translated at 100.0% (702 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/fr/

Translated using Weblate (Russian)

Currently translated at 100.0% (217 of 217 strings)

Translation: Habitica/Tasks
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/ru/

Translated using Weblate (Japanese)

Currently translated at 100.0% (217 of 217 strings)

Translation: Habitica/Tasks
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/ja/

Translated using Weblate (Italian)

Currently translated at 100.0% (328 of 328 strings)

Translation: Habitica/Front
Translate-URL: https://translate.habitica.com/projects/habitica/front/it/

Translated using Weblate (Russian)

Currently translated at 100.0% (124 of 124 strings)

Translation: Habitica/Communityguidelines
Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/ru/

Translated using Weblate (Russian)

Currently translated at 100.0% (92 of 92 strings)

Translation: Habitica/Achievements
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/ru/

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (550 of 550 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/vi/

Translated using Weblate (English (Pirate))

Currently translated at 87.5% (7 of 8 strings)

Translation: Habitica/Overview
Translate-URL: https://translate.habitica.com/projects/habitica/overview/en@pirate/

Translated using Weblate (French)

Currently translated at 100.0% (500 of 500 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/fr/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (2131 of 2131 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/zh_Hans/

Translated using Weblate (French)

Currently translated at 100.0% (134 of 134 strings)

Translation: Habitica/Challenge
Translate-URL: https://translate.habitica.com/projects/habitica/challenge/fr/

Translated using Weblate (English (Pirate))

Currently translated at 85.8% (79 of 92 strings)

Translation: Habitica/Achievements
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/en@pirate/

Translated using Weblate (French)

Currently translated at 100.0% (500 of 500 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/fr/

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (550 of 550 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/vi/

Translated using Weblate (French)

Currently translated at 100.0% (217 of 217 strings)

Translation: Habitica/Tasks
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/fr/

Translated using Weblate (French)

Currently translated at 100.0% (57 of 57 strings)

Translation: Habitica/Spells
Translate-URL: https://translate.habitica.com/projects/habitica/spells/fr/

Translated using Weblate (French)

Currently translated at 100.0% (2131 of 2131 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/fr/

Translated using Weblate (French)

Currently translated at 100.0% (56 of 56 strings)

Translation: Habitica/Faq
Translate-URL: https://translate.habitica.com/projects/habitica/faq/fr/

Translated using Weblate (French)

Currently translated at 100.0% (134 of 134 strings)

Translation: Habitica/Challenge
Translate-URL: https://translate.habitica.com/projects/habitica/challenge/fr/

Translated using Weblate (French)

Currently translated at 100.0% (550 of 550 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/fr/

Translated using Weblate (French)

Currently translated at 100.0% (92 of 92 strings)

Translation: Habitica/Achievements
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/fr/

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (550 of 550 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/vi/

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (217 of 217 strings)

Translation: Habitica/Tasks
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/vi/

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (92 of 92 strings)

Translation: Habitica/Achievements
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/vi/

Translated using Weblate (Vietnamese)

Currently translated at 82.5% (1760 of 2131 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/vi/

Translated using Weblate (Latin)

Currently translated at 39.6% (25 of 63 strings)

Translation: Habitica/Defaulttasks
Translate-URL: https://translate.habitica.com/projects/habitica/defaulttasks/la/

Translated using Weblate (Russian)

Currently translated at 98.2% (56 of 57 strings)

Translation: Habitica/Spells
Translate-URL: https://translate.habitica.com/projects/habitica/spells/ru/

Translated using Weblate (Russian)

Currently translated at 100.0% (146 of 146 strings)

Translation: Habitica/Pets
Translate-URL: https://translate.habitica.com/projects/habitica/pets/ru/

Translated using Weblate (Russian)

Currently translated at 100.0% (298 of 298 strings)

Translation: Habitica/Generic
Translate-URL: https://translate.habitica.com/projects/habitica/generic/ru/

Translated using Weblate (Russian)

Currently translated at 99.6% (548 of 550 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/ru/

Translated using Weblate (Russian)

Currently translated at 100.0% (92 of 92 strings)

Translation: Habitica/Achievements
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/ru/

Translated using Weblate (Russian)

Currently translated at 100.0% (212 of 212 strings)

Translation: Habitica/Settings
Translate-URL: https://translate.habitica.com/projects/habitica/settings/ru/

Translated using Weblate (Czech)

Currently translated at 100.0% (212 of 212 strings)

Translation: Habitica/Settings
Translate-URL: https://translate.habitica.com/projects/habitica/settings/cs/

Translated using Weblate (English (Pirate))

Currently translated at 88.0% (191 of 217 strings)

Translation: Habitica/Tasks
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/en@pirate/

Translated using Weblate (Czech)

Currently translated at 88.3% (1883 of 2131 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/cs/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (56 of 56 strings)

Translation: Habitica/Faq
Translate-URL: https://translate.habitica.com/projects/habitica/faq/zh_Hans/

Translated using Weblate (Japanese)

Currently translated at 91.0% (51 of 56 strings)

Translation: Habitica/Faq
Translate-URL: https://translate.habitica.com/projects/habitica/faq/ja/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (2131 of 2131 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/zh_Hans/

Translated using Weblate (Portuguese)

Currently translated at 100.0% (550 of 550 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/pt/

Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (2131 of 2131 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/zh_Hant/

Translated using Weblate (Czech)

Currently translated at 86.7% (1849 of 2131 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/cs/

Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (56 of 56 strings)

Translation: Habitica/Faq
Translate-URL: https://translate.habitica.com/projects/habitica/faq/zh_Hant/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (56 of 56 strings)

Translation: Habitica/Faq
Translate-URL: https://translate.habitica.com/projects/habitica/faq/zh_Hans/

Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (550 of 550 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/zh_Hant/

Translated using Weblate (Portuguese)

Currently translated at 87.2% (480 of 550 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/pt/

Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (217 of 217 strings)

Translation: Habitica/Tasks
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/zh_Hant/

Translated using Weblate (Italian)

Currently translated at 100.0% (217 of 217 strings)

Translation: Habitica/Tasks
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/it/

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (217 of 217 strings)

Translation: Habitica/Tasks
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/en_GB/

Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (57 of 57 strings)

Translation: Habitica/Spells
Translate-URL: https://translate.habitica.com/projects/habitica/spells/zh_Hant/

Translated using Weblate (Italian)

Currently translated at 100.0% (57 of 57 strings)

Translation: Habitica/Spells
Translate-URL: https://translate.habitica.com/projects/habitica/spells/it/

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (57 of 57 strings)

Translation: Habitica/Spells
Translate-URL: https://translate.habitica.com/projects/habitica/spells/en_GB/

Translated using Weblate (Czech)

Currently translated at 100.0% (57 of 57 strings)

Translation: Habitica/Spells
Translate-URL: https://translate.habitica.com/projects/habitica/spells/cs/

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (185 of 185 strings)

Translation: Habitica/Limited
Translate-URL: https://translate.habitica.com/projects/habitica/limited/pt_BR/

Translated using Weblate (Italian)

Currently translated at 100.0% (500 of 500 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/it/

Translated using Weblate (Turkish)

Currently translated at 80.0% (1706 of 2131 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/tr/

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (2131 of 2131 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/pt_BR/

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (2131 of 2131 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/pt_BR/

Translated using Weblate (Italian)

Currently translated at 100.0% (2131 of 2131 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/it/

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (2131 of 2131 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/en_GB/

Translated using Weblate (Italian)

Currently translated at 100.0% (56 of 56 strings)

Translation: Habitica/Faq
Translate-URL: https://translate.habitica.com/projects/habitica/faq/it/

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (56 of 56 strings)

Translation: Habitica/Faq
Translate-URL: https://translate.habitica.com/projects/habitica/faq/en_GB/

Translated using Weblate (Italian)

Currently translated at 100.0% (550 of 550 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/it/

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (550 of 550 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/en_GB/

Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (92 of 92 strings)

Translation: Habitica/Achievements
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/zh_Hant/

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (92 of 92 strings)

Translation: Habitica/Achievements
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/pt_BR/

Translated using Weblate (Italian)

Currently translated at 100.0% (92 of 92 strings)

Translation: Habitica/Achievements
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/it/

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (92 of 92 strings)

Translation: Habitica/Achievements
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/en_GB/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (56 of 56 strings)

Translation: Habitica/Faq
Translate-URL: https://translate.habitica.com/projects/habitica/faq/zh_Hans/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (217 of 217 strings)

Translation: Habitica/Tasks
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/zh_Hans/

Translated using Weblate (Russian)

Currently translated at 99.5% (216 of 217 strings)

Translation: Habitica/Tasks
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/ru/

Translated using Weblate (German)

Currently translated at 100.0% (217 of 217 strings)

Translation: Habitica/Tasks
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/de/

Translated using Weblate (Russian)

Currently translated at 95.2% (239 of 251 strings)

Translation: Habitica/Subscriber
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/ru/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (57 of 57 strings)

Translation: Habitica/Spells
Translate-URL: https://translate.habitica.com/projects/habitica/spells/zh_Hans/

Translated using Weblate (German)

Currently translated at 100.0% (57 of 57 strings)

Translation: Habitica/Spells
Translate-URL: https://translate.habitica.com/projects/habitica/spells/de/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (146 of 146 strings)

Translation: Habitica/Pets
Translate-URL: https://translate.habitica.com/projects/habitica/pets/zh_Hans/

Translated using Weblate (Russian)

Currently translated at 98.4% (492 of 500 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/ru/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (2131 of 2131 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/zh_Hans/

Translated using Weblate (Russian)

Currently translated at 96.1% (2048 of 2131 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ru/

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (2131 of 2131 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/pt_BR/

Translated using Weblate (German)

Currently translated at 99.8% (2128 of 2131 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/de/

Translated using Weblate (Chinese (Simplified))

Currently translated at 92.8% (52 of 56 strings)

Translation: Habitica/Faq
Translate-URL: https://translate.habitica.com/projects/habitica/faq/zh_Hans/

Translated using Weblate (Russian)

Currently translated at 94.6% (53 of 56 strings)

Translation: Habitica/Faq
Translate-URL: https://translate.habitica.com/projects/habitica/faq/ru/

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (56 of 56 strings)

Translation: Habitica/Faq
Translate-URL: https://translate.habitica.com/projects/habitica/faq/pt_BR/

Translated using Weblate (German)

Currently translated at 98.2% (55 of 56 strings)

Translation: Habitica/Faq
Translate-URL: https://translate.habitica.com/projects/habitica/faq/de/

Translated using Weblate (Turkish)

Currently translated at 99.1% (228 of 230 strings)

Translation: Habitica/Character
Translate-URL: https://translate.habitica.com/projects/habitica/character/tr/

Translated using Weblate (Turkish)

Currently translated at 99.1% (228 of 230 strings)

Translation: Habitica/Character
Translate-URL: https://translate.habitica.com/projects/habitica/character/tr/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (550 of 550 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/zh_Hans/

Translated using Weblate (Russian)

Currently translated at 98.7% (543 of 550 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/ru/

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (550 of 550 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/pt_BR/

Translated using Weblate (German)

Currently translated at 99.8% (549 of 550 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/de/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (92 of 92 strings)

Translation: Habitica/Achievements
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/zh_Hans/

Translated using Weblate (Russian)

Currently translated at 100.0% (92 of 92 strings)

Translation: Habitica/Achievements
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/ru/

Translated using Weblate (German)

Currently translated at 100.0% (92 of 92 strings)

Translation: Habitica/Achievements
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/de/

Translated using Weblate (Turkish)

Currently translated at 99.5% (229 of 230 strings)

Translation: Habitica/Character
Translate-URL: https://translate.habitica.com/projects/habitica/character/tr/
2020-09-01 19:42:34 +02:00
Sabe Jones
01ae56f944 Merge branch 'release' into develop 2020-09-01 11:10:26 -05:00
Sabe Jones
7b020e133f 4.155.0 2020-09-01 11:09:37 -05:00
Sabe Jones
ca413ff41a chore(sprites): compile 2020-09-01 11:08:50 -05:00
Sabe Jones
81eef79da4 feat(content): Mystery Items and Challenges 2020-09-01 11:08:41 -05:00
negue
f1469b52f6 hotfix datepicker size 2020-08-31 23:13:31 +02:00
Matteo Pagliazzi
ef118f23c2 fix(loading): do not shift loading screen 2020-08-31 16:54:31 +02:00
dependabot-preview[bot]
964861bd6c build(deps-dev): bump axios from 0.19.2 to 0.20.0 (#12492)
Bumps [axios](https://github.com/axios/axios) from 0.19.2 to 0.20.0.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/master/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v0.19.2...v0.20.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-08-31 14:10:32 +02:00
dependabot-preview[bot]
32e9dbe1ed build(deps): bump @vue/cli-plugin-unit-mocha in /website/client (#12513)
Bumps [@vue/cli-plugin-unit-mocha](https://github.com/vuejs/vue-cli/tree/HEAD/packages/@vue/cli-plugin-unit-mocha) from 4.5.3 to 4.5.4.
- [Release notes](https://github.com/vuejs/vue-cli/releases)
- [Changelog](https://github.com/vuejs/vue-cli/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/vuejs/vue-cli/commits/v4.5.4/packages/@vue/cli-plugin-unit-mocha)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-08-31 13:25:28 +02:00
dependabot-preview[bot]
466fb4e42e build(deps): bump axios from 0.19.2 to 0.20.0 in /website/client (#12516)
Bumps [axios](https://github.com/axios/axios) from 0.19.2 to 0.20.0.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/master/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v0.19.2...v0.20.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-08-31 13:24:47 +02:00
dependabot-preview[bot]
8f923f7753 build(deps): bump mongoose from 5.9.29 to 5.10.2 (#12503)
Bumps [mongoose](https://github.com/Automattic/mongoose) from 5.9.29 to 5.10.2.
- [Release notes](https://github.com/Automattic/mongoose/releases)
- [Changelog](https://github.com/Automattic/mongoose/blob/master/History.md)
- [Commits](https://github.com/Automattic/mongoose/compare/5.9.29...5.10.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-08-31 13:24:01 +02:00
Matteo Pagliazzi
3bb8db45fd build(deps): bump @vue/cli-plugin-eslint in /website/client (#12510)
Bumps [@vue/cli-plugin-eslint](https://github.com/vuejs/vue-cli/tree/HEAD/packages/@vue/cli-plugin-eslint) from 4.5.3 to 4.5.4.
- [Release notes](https://github.com/vuejs/vue-cli/releases)
- [Changelog](https://github.com/vuejs/vue-cli/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/vuejs/vue-cli/commits/v4.5.4/packages/@vue/cli-plugin-eslint)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-08-31 13:22:56 +02:00
dependabot-preview[bot]
181317dbff build(deps): bump @vue/cli-plugin-eslint in /website/client
Bumps [@vue/cli-plugin-eslint](https://github.com/vuejs/vue-cli/tree/HEAD/packages/@vue/cli-plugin-eslint) from 4.5.3 to 4.5.4.
- [Release notes](https://github.com/vuejs/vue-cli/releases)
- [Changelog](https://github.com/vuejs/vue-cli/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/vuejs/vue-cli/commits/v4.5.4/packages/@vue/cli-plugin-eslint)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-08-31 11:22:44 +00:00
dependabot-preview[bot]
73a7ef8b2c build(deps): bump vue and vue-template-compiler in /website/client (#12511)
Bumps [vue](https://github.com/vuejs/vue) and [vue-template-compiler](https://github.com/vuejs/vue). These dependencies needed to be updated together.

Updates `vue` from 2.6.11 to 2.6.12
- [Release notes](https://github.com/vuejs/vue/releases)
- [Commits](https://github.com/vuejs/vue/compare/v2.6.11...v2.6.12)

Updates `vue-template-compiler` from 2.6.11 to 2.6.12
- [Release notes](https://github.com/vuejs/vue/releases)
- [Commits](https://github.com/vuejs/vue/compare/v2.6.11...v2.6.12)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-08-31 13:21:15 +02:00
dependabot-preview[bot]
206e3468f4 build(deps): bump vuedraggable from 2.24.0 to 2.24.1 in /website/client (#12520)
Bumps [vuedraggable](https://github.com/SortableJS/Vue.Draggable) from 2.24.0 to 2.24.1.
- [Release notes](https://github.com/SortableJS/Vue.Draggable/releases)
- [Commits](https://github.com/SortableJS/Vue.Draggable/commits)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-08-31 13:17:49 +02:00
dependabot-preview[bot]
77fde9d73f build(deps): bump amplitude-js from 7.1.0 to 7.1.1 in /website/client (#12517)
Bumps [amplitude-js](https://github.com/amplitude/amplitude-javascript) from 7.1.0 to 7.1.1.
- [Release notes](https://github.com/amplitude/amplitude-javascript/releases)
- [Changelog](https://github.com/amplitude/Amplitude-JavaScript/blob/master/CHANGELOG.md)
- [Commits](https://github.com/amplitude/amplitude-javascript/compare/v7.1.0...v7.1.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-08-31 13:17:25 +02:00
dependabot-preview[bot]
c1d4bcbac3 build(deps): bump @vue/cli-service in /website/client (#12514)
Bumps [@vue/cli-service](https://github.com/vuejs/vue-cli/tree/HEAD/packages/@vue/cli-service) from 4.5.3 to 4.5.4.
- [Release notes](https://github.com/vuejs/vue-cli/releases)
- [Changelog](https://github.com/vuejs/vue-cli/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/vuejs/vue-cli/commits/v4.5.4/packages/@vue/cli-service)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-08-31 13:17:05 +02:00
dependabot-preview[bot]
e98d0d88d9 build(deps): bump @storybook/addon-notes in /website/client (#12512)
Bumps [@storybook/addon-notes](https://github.com/storybookjs/storybook/tree/HEAD/addons/notes) from 5.3.19 to 5.3.21.
- [Release notes](https://github.com/storybookjs/storybook/releases)
- [Changelog](https://github.com/storybookjs/storybook/blob/v5.3.21/CHANGELOG.md)
- [Commits](https://github.com/storybookjs/storybook/commits/v5.3.21/addons/notes)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-08-31 13:16:39 +02:00
dependabot-preview[bot]
fc3a1dd93d build(deps): bump @vue/cli-plugin-router in /website/client (#12509)
Bumps [@vue/cli-plugin-router](https://github.com/vuejs/vue-cli/tree/HEAD/packages/@vue/cli-plugin-router) from 4.5.3 to 4.5.4.
- [Release notes](https://github.com/vuejs/vue-cli/releases)
- [Changelog](https://github.com/vuejs/vue-cli/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/vuejs/vue-cli/commits/v4.5.4/packages/@vue/cli-plugin-router)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-08-31 13:16:07 +02:00
dependabot-preview[bot]
897155e4c8 build(deps): bump @vue/cli-plugin-babel in /website/client (#12506)
Bumps [@vue/cli-plugin-babel](https://github.com/vuejs/vue-cli/tree/HEAD/packages/@vue/cli-plugin-babel) from 4.5.3 to 4.5.4.
- [Release notes](https://github.com/vuejs/vue-cli/releases)
- [Changelog](https://github.com/vuejs/vue-cli/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/vuejs/vue-cli/commits/v4.5.4/packages/@vue/cli-plugin-babel)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-08-31 13:15:55 +02:00
dependabot-preview[bot]
689f5ad634 build(deps): bump superagent from 6.0.0 to 6.1.0 (#12502)
Bumps [superagent](https://github.com/visionmedia/superagent) from 6.0.0 to 6.1.0.
- [Release notes](https://github.com/visionmedia/superagent/releases)
- [Changelog](https://github.com/visionmedia/superagent/blob/master/HISTORY.md)
- [Commits](https://github.com/visionmedia/superagent/compare/v6.0.0...v6.1.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-08-31 13:15:45 +02:00
negue
aa1ea74daa datepicker: add clear button (#12480)
* datepicker: add clear button

* use close icon instead of "x" as text
2020-08-29 17:22:31 +02:00
Bart Enkelaar
1b301e9c68 issue(11266) - Restyle level-up modal with sparkles (#12486)
* issue(11266) - Restyle level-up modal with sparkles

* issue(11266) - Add reward display to level up modal

At levels 15, 30, 40 and 60 the earned quests are now shown in the level-up modal.

* issue(11266) - Simplify css and don't use custom footer

* issue(11266) - Don't show pink bars and use colour variables
2020-08-29 16:33:06 +02:00
Matteo Pagliazzi
c00b2247d4 fix #12498: do not score task when using a skill 2020-08-29 16:23:32 +02:00
Alys
88de5552a0 change blocked message 2020-08-29 18:03:59 +10:00
Sabe Jones
917c68d51f 4.154.1 2020-08-27 14:44:49 -05:00
Sabe Jones
44e438303a chore(news): Last Chance 2020-08-27 14:44:43 -05:00
Sabe Jones
7eeddcb033 Merge branch 'release' into develop 2020-08-25 16:05:40 -05:00
Sabe Jones
2fb8e16e8f 4.154.0 2020-08-25 16:05:13 -05:00
Sabe Jones
80ffcddb35 chore(sprites): compile 2020-08-25 16:04:35 -05:00
Sabe Jones
f4c453675b feat(content): Wind-Up Potions quest 2020-08-25 16:04:28 -05:00
Alys
5165d491b0 fix some lint warnings (#12488)
* prevent some lint warnings by turning off some rules we don't seem to care about

require-prop-types has 167 violations

require-default-prop has 93 violations

* fix some lint warnings by adding eslint-disable-line comments

* prevent lint warnings by moving vue hooks to a different order

* apply some automatic lint fixes

* fix lint error by making line shorter

* prevent lint warnings from whitespace and @input, @click positioning

* revert 723fa38271

Reenables vue/require-prop-types and vue/require-default-prop
2020-08-25 18:06:39 +02:00
Sara Olson
6559353613 Update faq.json
some more android and ios faq changes
2020-08-25 18:01:37 +02:00
Matteo Pagliazzi
9caacc8f6c fix(links): restore styles changed in bootstrap@4.5.1 2020-08-25 16:17:12 +02:00
dependabot-preview[bot]
b21b5a4f4b build(deps): bump jwks-rsa from 1.8.1 to 1.9.0 (#12493)
Bumps [jwks-rsa](https://github.com/auth0/node-jwks-rsa) from 1.8.1 to 1.9.0.
- [Release notes](https://github.com/auth0/node-jwks-rsa/releases)
- [Changelog](https://github.com/auth0/node-jwks-rsa/blob/master/CHANGELOG.md)
- [Commits](https://github.com/auth0/node-jwks-rsa/compare/v1.8.1...v1.9.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-08-24 18:23:12 +02:00
dependabot-preview[bot]
21f3e704d4 build(deps): bump @babel/core from 7.11.1 to 7.11.4 (#12489)
Bumps [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core) from 7.11.1 to 7.11.4.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.11.4/packages/babel-core)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-08-24 14:26:14 +02:00
Matteo Pagliazzi
d0bc0dbe49 Add API Call to bulk score tasks (#11389)
* Add new API call to complete multiple task scorings in one call

* Improve API response

* Improve saving process

* Improve handling for multiple tasks scored at once

* Handle challenge task errors better

* Improve check for alias

* Improve check for task scorings

* Fix merge errors

* make nodemon ignore content_cache

* Fix completing group tasks

* fix test

* fix tests (again)

* typo

* WIP(a11y): task modal updates

* fix(tasks): borders in modal

* fix(tasks): circley locks

* fix(task-modal): placeholders

* WIP(task-modal): disabled states, hide empty options, +/- restyle

* fix(task-modal): box shadows instead of borders, habit control pointer

* fix(task-modal): button states?

* fix(modal): tighten up layout, new spacing utils

* fix(tasks): more stylin

* fix(tasks): habit hovers

* fix(css): checklist labels, a11y colors

* fix(css): one more missed hover issue

* fix(css): lock Challenges, label fixes

* fix(css): scope input/textarea changes

* fix(style): task tweakies

* fix(style): more button fixage

* WIP(component): start select list story

* working example of a templated selectList

* fix(style): more button corrections

* fix(lint): EOL

* fix(buttons): factor btn-secondary to better override Bootstrap

* fix(styles): standardize more buttons

* wip: difficulty select - style fixes

* selectDifficulty works! 🎉 - fix styles

* change the dropdown-item sizes only for the selectList ones

* selectTranslatedArray

* changed many label margins

* more correct dropdown style

* fix(modals): button corrections

* input-group styling + datetime picker without today button

* Style/margins for "repeat every" - extract selectTag.vue

* working tag-selection / update - cleanup

* fix stories

* fix svg color on create modal (purple)

* fix task modal bottom padding

* correct dropdown shadow

* update dropdown-toggle caret size / color

* fixed checklist style

* sync checked state

* selectTag padding

* fix spacing between positive/negative streak inputs

* toggle-checkbox + fix some spacings

* disable repeat-on when its a groupTask

* fix new checklist-item

* fix toggle-checkbox style - fix difficulty style

* fix checklist ui

* add tags label , when there arent any tags selected

* WORKING select-tag component 🎉

* fix taglist story

* show max 5 items in tag dropdown + "X more" label

* fix datetime clear button

* replace m-b-xs to mb-1 (bootstrap) - fix input-group-text style

* fix styles of advanced settings

* fix delete task styles

* always show grippy on hover of the item

* extract modal-text-input mixin + fix the borders/dropshadow

* fix(spacing): revert most to Bootstrap

* feat(checklists): make local copy of master checklist non-editable
also aggressively update checklists because they weren't syncing??

* fix(checklists): handle add/remove options better

* feat(teams): manager notes field

* fix select/dropdown styles

* input border + icon colors

* delete task underline color

* fix checklist "delete icon" vertical position

* selectTag fixes - normal open/close toggle working again - remove icon color

* fixing icons:

Trash can - Delete
Little X - Remove
Big X - Close
Block - Block

* fix taglist margins / icon sizes

* wip margin overview (in storybook)

* fix routerlink

* remove unused method

* new selectTag style + add markdown inside tagList + scrollable tag selection

* fix selectTag / selectList active border

* fix difficulty select (svg default color)

* fix input padding-left + fix reset habit streak fullwidth / padding + "repeat every" gray text (no border)

* feat(teams): improved approval request > approve > reward flow

* fix(tests): address failures

* fix(lint): oops only

* fix(tasks): short-circuit group related logic

* fix(tasks): more short circuiting

* fix(tasks): more lines, less lint

* fix(tasks): how do i keep missing these

* feat(teams): provide assigning user summary

* fix(teams): don't attempt to record assiging user if not supplied

* fix advanced-settings styling / margin

* fix merge + hide advanced streak settings when none enabled

* fix styles

* set Roboto font for advanced settings

* Add Challenge flag to the tag list

* add tag with enter, when no other tag is found

* fix styles + tag cancel button

* refactor footer / margin

* split repeat fields into option mt-3 groups

* button all the things

* fix(tasks): style updates
* no hover state for non-editable tasks on team board
* keep assign/claim footer on task after requesting approval
* disable more fields on user copy of team task, and remove hover states 
for them

* fix(tasks): functional revisions
* "Claim Rewards" instead of "x" in task approved notif
* Remove default transition supplied by Bootstrap, apply individually to 
some elements
* Delete individual tasks and related notifications when master task 
deleted from team board
* Manager notes now save when supplied at task initial creation
* Can no longer dismiss rewards from approved task by hitting Dismiss 
All

* fix(tasks): clean tasksOrder
also adjust related test expectation

* fix(tests): adjust integration expectations

* fix(test): ratzen fratzen only

* fix lint

* fix tests

* fix(teams): checklist, notes

* handleSharedCompletion: handle error, make sure it is run after the user task has been saved

* fix typo

* correctly handle errors in handleSharedCompletion when approving a task

* fix(teams): improve disabled states

* handleSharedCompletion: do not increase completions by 1 manually to adjust for last approval not saved yet

* revert changes to config.json.example

* fix(teams): more style fixage

* add unit tests for findMultipleByIdOrAlias

* exclude api v4 route from apidocs

* BREAKING(teams): return 202 instead of 401 for approval request

* fix(teams): better taskboard sync
also re-re-fix checklist borders

* scoreTasks: validate body

* fix tests, move string to api errors

* fix(tests): update expectations for breaking change

* start updating api docs, process tasks sequentially to avoid conflicts with user._tmp

* do not crash entire bulk operation in case of errors

* save task only if modified

* fix lint

* undo changes to error handling: either all tasks scoring are successfull or none

* remove stale code

* do not return user._tmp when bulk scoring, it would be the last version only

* make sure user._tmp.leveledUp is not lost when bulk scoring

* rewards tests

* mixed tests

* fix tests, allow scoring the same task multiple times

* finish integration tests

* fix api docs for the bulk score route

* refactor(task-modal): lockable label component

* wip loading spinner

* refactor(teams): move task scoring to mixin

* fix(teams): style corrections

* fix(btn): fix padding to have height of 32px

* implement loading spinner

* remove console.log warnings

* fix(tasks): spacing and wording corrections

* fix(teams): don't bork manager notes

* fix(teams): assignment fix and more approval flow revisions

* WIP(teams): use tag dropdown control for assignment

* finish merge - never throw an error when a group task requires approval (wip - needs tests)

* fix taskModal merge

* fix merge

* fix(task modal): add newline

* fix(column.vue): add newline at end of file

* mvp yesterdaily modal

* fix tests

* fix api docs for bulk scoring group tasks

* separate task scoring and _tmp handling

* handle _tmp when bulk scoring

* rya: close modal before calling cron API, prevents issues with modals

* rya: fix conflicts with other modals

* add sounds, support for group plans, analytics

* use asyncResource for group plans

* fix lint

* streak bonus: add comment about missing in rya

* move yesterdailyModal

* fix issues with level up modals and rya

* add comments for future use, fix level up modals not showing up at levels with a quest drop

* handle errors in rya modal

* bundle quest and crit notifications

Co-authored-by: Phillip Thelen <phillip@habitica.com>
Co-authored-by: Phillip Thelen <viirus@pherth.net>
Co-authored-by: Sabe Jones <sabrecat@gmail.com>
Co-authored-by: negue <eugen.bolz@gmail.com>
2020-08-21 11:46:56 +02:00
Matteo Pagliazzi
46b5efcaf6 Allow group plans for private guilds only (#12479)
* hide upgrade button for public guilds

* prevent group plans for public guilds

* fix unit tests

* fix integration and unit tests that assumed group plans could be public guilds

* more unit tests fixes

* more resilient unit test

* more resilient unit test
2020-08-21 11:46:09 +02:00
Melior
633f3df372 Translated using Weblate (Turkish)
Currently translated at 96.3% (209 of 217 strings)

Translation: Habitica/Tasks
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/tr/

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (217 of 217 strings)

Translation: Habitica/Tasks
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/pt_BR/

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (57 of 57 strings)

Translation: Habitica/Spells
Translate-URL: https://translate.habitica.com/projects/habitica/spells/pt_BR/

Translated using Weblate (Portuguese (Brazil))

Currently translated at 99.9% (2130 of 2131 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/pt_BR/

Translated using Weblate (Portuguese (Brazil))

Currently translated at 98.2% (55 of 56 strings)

Translation: Habitica/Faq
Translate-URL: https://translate.habitica.com/projects/habitica/faq/pt_BR/

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (92 of 92 strings)

Translation: Habitica/Achievements
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/pt_BR/
2020-08-20 22:45:40 +02:00
Sabe Jones
fa79fb6608 4.153.1 2020-08-20 14:47:26 -05:00
Sabe Jones
f9d9df5ddb chore(news): blogs 2020-08-20 14:47:18 -05:00
Sabe Jones
8dbbfcd3a1 Scheduling summary line for team Dailies and To Do's (#12439)
* WIP(dailies): summary sentence draft

* fix(dailies): hide summarized fields, handle future Daily start date

* fix(dailies): don't apply summary sentence to Challenges
...for the sake of non-English users

* fix(dailies): tweak date x Challenge logic

* refactor(dailies): use native JS forEach
2020-08-20 14:12:05 -05:00
Matteo Pagliazzi
9a07ba7417 Better XSS Fix (#12483)
* Revert "fix(test): adjust expectations"

This reverts commit 205436d5b1.

* Revert "fix(escaping): global inoffensive apostrophe"

This reverts commit 2b8f94b244.

* change <%- to <%=

* fix interpolation only where necessary

* remove unused variable
2020-08-20 13:41:46 -05:00
Phillip Thelen
8248c4ca4e Update faq.json (#12484) 2020-08-19 19:59:48 +02:00
Sabe Jones
0e9ac6d4f2 Merge branch 'release' into develop 2020-08-18 15:29:03 -05:00
Sabe Jones
56df62cf49 4.153.0 2020-08-18 15:27:12 -05:00
Sabe Jones
eb16966953 fix(news): image 2020-08-18 15:26:54 -05:00
Sabe Jones
6255c5dcc7 feat(content): golden achievements 2020-08-18 15:13:06 -05:00
Matteo Pagliazzi
60ffc1fdaf fix(mongoose): revert to 5.9.29 2020-08-17 13:08:50 +02:00
Alexander Colen
0cfe0473b9 Remove Tavern from API v3 list of guilds when 'guild' or 'publicGuilds' type parameter get added. (Fixes #12407) (#12438)
* Excluding tavern from showing up in GET /groups API when 'guilds' or 'publicGuilds' type parameter is included.

* Fixed test errors.

* Resolved pull request #12438 issues.

Moved Tavern exclusion to Group model, removed Group controller back to the original and resolved test failures.
2020-08-17 12:38:07 +02:00
Matteo Pagliazzi
10f89c8d79 chore(vue deps): upgrade 2020-08-17 09:41:42 +02:00
dependabot-preview[bot]
0692eb10cc build(deps): bump lodash from 4.17.19 to 4.17.20 (#12461)
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.19 to 4.17.20.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.19...4.17.20)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-08-17 09:30:30 +02:00
dependabot-preview[bot]
4ac160ab21 build(deps): bump lodash from 4.17.19 to 4.17.20 in /website/client (#12468)
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.19 to 4.17.20.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.19...4.17.20)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-08-17 09:28:54 +02:00
dependabot-preview[bot]
dc5163e8a0 build(deps): bump vue-router from 3.4.2 to 3.4.3 in /website/client (#12478)
Bumps [vue-router](https://github.com/vuejs/vue-router) from 3.4.2 to 3.4.3.
- [Release notes](https://github.com/vuejs/vue-router/releases)
- [Changelog](https://github.com/vuejs/vue-router/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/vuejs/vue-router/compare/v3.4.2...v3.4.3)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-08-17 09:28:22 +02:00
dependabot-preview[bot]
d850b50009 build(deps): bump winston-loggly-bulk from 3.1.0 to 3.1.1 (#12465)
Bumps [winston-loggly-bulk](https://github.com/loggly/winston-loggly-bulk) from 3.1.0 to 3.1.1.
- [Release notes](https://github.com/loggly/winston-loggly-bulk/releases)
- [Commits](https://github.com/loggly/winston-loggly-bulk/compare/v3.1.0...v3.1.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-08-17 09:27:09 +02:00
dependabot-preview[bot]
65e00ef784 build(deps-dev): bump sinon from 9.0.2 to 9.0.3 (#12467)
Bumps [sinon](https://github.com/sinonjs/sinon) from 9.0.2 to 9.0.3.
- [Release notes](https://github.com/sinonjs/sinon/releases)
- [Changelog](https://github.com/sinonjs/sinon/blob/master/CHANGELOG.md)
- [Commits](https://github.com/sinonjs/sinon/compare/v9.0.2...v9.0.3)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-08-17 09:26:40 +02:00
dependabot-preview[bot]
fd11adbb82 build(deps): bump apidoc from 0.24.0 to 0.25.0 (#12466)
Bumps [apidoc](https://github.com/apidoc/apidoc) from 0.24.0 to 0.25.0.
- [Release notes](https://github.com/apidoc/apidoc/releases)
- [Changelog](https://github.com/apidoc/apidoc/blob/master/CHANGELOG.md)
- [Commits](https://github.com/apidoc/apidoc/compare/0.24.0...0.25.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-08-17 09:26:31 +02:00
dependabot-preview[bot]
4d64e299ef build(deps): bump mongoose from 5.9.27 to 5.10.0 (#12463)
Bumps [mongoose](https://github.com/Automattic/mongoose) from 5.9.27 to 5.10.0.
- [Release notes](https://github.com/Automattic/mongoose/releases)
- [Changelog](https://github.com/Automattic/mongoose/blob/master/History.md)
- [Commits](https://github.com/Automattic/mongoose/compare/5.9.27...5.10.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-08-17 09:26:02 +02:00
Sabe Jones
29c21f09e1 4.152.3 2020-08-15 12:16:52 -05:00
Sabe Jones
205436d5b1 fix(test): adjust expectations 2020-08-15 11:56:47 -05:00
Sabe Jones
2b8f94b244 fix(escaping): global inoffensive apostrophe 2020-08-15 10:27:37 -05:00
Sabe Jones
30cedad9b2 4.152.2 2020-08-14 11:18:44 -05:00
Sabe Jones
3a67a36031 fix(escaping): Armoire and boss messages 2020-08-14 11:04:21 -05:00
Sabe Jones
9d645c1c2e fix(merge): missing string 2020-08-13 17:38:05 -05:00
Sabe Jones
6ee4c9870a fix(merge): missing string 2020-08-13 17:27:51 -05:00
Sabe Jones
fbfe35b4eb 4.152.1 2020-08-13 17:12:09 -05:00
Sabe Jones
3b9509fa1a Merge remote-tracking branch 'habitica-private/xss-fix' into release 2020-08-13 17:11:48 -05:00
Sabe Jones
0f81c5cbdb Merge branch 'develop' into release 2020-08-13 16:21:21 -05:00
Sabe Jones
d3fdfe33fb chore(news): Bailey 2020-08-13 16:20:52 -05:00
Sabe Jones
d9cf7d3f79 fix(gdpr): handle blocked user; pace out requests 2020-08-13 15:35:11 +00:00
Melior
ffe144e762 Merge branch 'origin/develop' into Weblate. 2020-08-11 22:47:12 +02:00
Sabe Jones
cec3e67b16 Merge branch 'release' into develop 2020-08-11 15:44:48 -05:00
Sabe Jones
2a94ff41b1 4.152.0 2020-08-11 15:43:51 -05:00
Sabe Jones
ce32477af7 chore(sprites): compile 2020-08-11 15:43:38 -05:00
Sabe Jones
1b6b99b521 feat(content): Armoire and backgrounds 8/2020 2020-08-11 15:43:29 -05:00
Matteo Pagliazzi
b7964a411c fix(i18n): html-escape all interpolated strings where html is not necessary 2020-08-11 21:33:24 +02:00
Melior
bce138f9c2 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (702 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/zh_Hans/

Translated using Weblate (Japanese)

Currently translated at 90.4% (1923 of 2125 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ja/

Translated using Weblate (Czech)

Currently translated at 86.7% (1843 of 2125 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/cs/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (251 of 251 strings)

Translation: Habitica/Subscriber
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/zh_Hans/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (2125 of 2125 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/zh_Hans/

Translated using Weblate (Filipino)

Currently translated at 80.0% (1701 of 2125 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/fil/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (2125 of 2125 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/zh_Hans/

Translated using Weblate (Czech)

Currently translated at 86.3% (1834 of 2125 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/cs/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (702 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/zh_Hans/

Translated using Weblate (Korean)

Currently translated at 80.9% (51 of 63 strings)

Translation: Habitica/Defaulttasks
Translate-URL: https://translate.habitica.com/projects/habitica/defaulttasks/ko/

Translated using Weblate (Korean)

Currently translated at 95.2% (343 of 360 strings)

Translation: Habitica/Content
Translate-URL: https://translate.habitica.com/projects/habitica/content/ko/

Translated using Weblate (Korean)

Currently translated at 88.8% (192 of 216 strings)

Translation: Habitica/Tasks
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/ko/

Translated using Weblate (Czech)

Currently translated at 85.8% (1824 of 2125 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/cs/

Translated using Weblate (Czech)

Currently translated at 85.7% (1823 of 2125 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/cs/

Translated using Weblate (Czech)

Currently translated at 98.6% (213 of 216 strings)

Translation: Habitica/Tasks
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/cs/

Translated using Weblate (Czech)

Currently translated at 85.4% (1816 of 2125 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/cs/

Translated using Weblate (Italian)

Currently translated at 100.0% (173 of 173 strings)

Translation: Habitica/Npc
Translate-URL: https://translate.habitica.com/projects/habitica/npc/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (8 of 8 strings)

Translation: Habitica/Overview
Translate-URL: https://translate.habitica.com/projects/habitica/overview/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (493 of 493 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (328 of 328 strings)

Translation: Habitica/Front
Translate-URL: https://translate.habitica.com/projects/habitica/front/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (124 of 124 strings)

Translation: Habitica/Communityguidelines
Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (212 of 212 strings)

Translation: Habitica/Settings
Translate-URL: https://translate.habitica.com/projects/habitica/settings/it/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (2125 of 2125 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/zh_Hans/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (702 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/zh_Hans/

Translated using Weblate (Czech)

Currently translated at 100.0% (56 of 56 strings)

Translation: Habitica/Faq
Translate-URL: https://translate.habitica.com/projects/habitica/faq/cs/

Translated using Weblate (Czech)

Currently translated at 100.0% (56 of 56 strings)

Translation: Habitica/Faq
Translate-URL: https://translate.habitica.com/projects/habitica/faq/cs/

Translated using Weblate (Czech)

Currently translated at 91.0% (51 of 56 strings)

Translation: Habitica/Faq
Translate-URL: https://translate.habitica.com/projects/habitica/faq/cs/

Translated using Weblate (Polish)

Currently translated at 100.0% (251 of 251 strings)

Translation: Habitica/Subscriber
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/pl/

Translated using Weblate (Japanese)

Currently translated at 100.0% (67 of 67 strings)

Translation: Habitica/Messages
Translate-URL: https://translate.habitica.com/projects/habitica/messages/ja/

Translated using Weblate (Polish)

Currently translated at 99.4% (497 of 500 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/pl/

Translated using Weblate (Japanese)

Currently translated at 99.8% (499 of 500 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/ja/

Translated using Weblate (Polish)

Currently translated at 81.5% (1732 of 2125 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/pl/

Translated using Weblate (Japanese)

Currently translated at 90.4% (1921 of 2125 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ja/

Translated using Weblate (Polish)

Currently translated at 100.0% (124 of 124 strings)

Translation: Habitica/Communityguidelines
Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/pl/

Translated using Weblate (Polish)

Currently translated at 100.0% (86 of 86 strings)

Translation: Habitica/Achievements
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/pl/

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (140 of 140 strings)

Translation: Habitica/Quests
Translate-URL: https://translate.habitica.com/projects/habitica/quests/vi/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (702 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/zh_Hans/

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (2125 of 2125 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/pt_BR/

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (2125 of 2125 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/pt_BR/

Translated using Weblate (Czech)

Currently translated at 100.0% (328 of 328 strings)

Translation: Habitica/Front
Translate-URL: https://translate.habitica.com/projects/habitica/front/cs/

Translated using Weblate (Czech)

Currently translated at 91.0% (51 of 56 strings)

Translation: Habitica/Faq
Translate-URL: https://translate.habitica.com/projects/habitica/faq/cs/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (2125 of 2125 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/zh_Hans/

Translated using Weblate (Japanese)

Currently translated at 90.3% (1919 of 2125 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ja/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (702 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/zh_Hans/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (702 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/zh_Hans/

Translated using Weblate (Dutch)

Currently translated at 100.0% (2125 of 2125 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/nl/

Translated using Weblate (Japanese)

Currently translated at 90.1% (1915 of 2125 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ja/

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (298 of 298 strings)

Translation: Habitica/Generic
Translate-URL: https://translate.habitica.com/projects/habitica/generic/pt_BR/

Translated using Weblate (Czech)

Currently translated at 85.4% (1815 of 2125 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/cs/

Translated using Weblate (Czech)

Currently translated at 87.5% (49 of 56 strings)

Translation: Habitica/Faq
Translate-URL: https://translate.habitica.com/projects/habitica/faq/cs/

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (216 of 216 strings)

Translation: Habitica/Tasks
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/vi/

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (251 of 251 strings)

Translation: Habitica/Subscriber
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/vi/

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (86 of 86 strings)

Translation: Habitica/Achievements
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/vi/

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (500 of 500 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/vi/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (702 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/zh_Hans/

Translated using Weblate (Dutch)

Currently translated at 100.0% (146 of 146 strings)

Translation: Habitica/Pets
Translate-URL: https://translate.habitica.com/projects/habitica/pets/nl/

Translated using Weblate (Dutch)

Currently translated at 100.0% (31 of 31 strings)

Translation: Habitica/Maintenance
Translate-URL: https://translate.habitica.com/projects/habitica/maintenance/nl/

Translated using Weblate (Dutch)

Currently translated at 100.0% (31 of 31 strings)

Translation: Habitica/Maintenance
Translate-URL: https://translate.habitica.com/projects/habitica/maintenance/nl/

Translated using Weblate (Dutch)

Currently translated at 100.0% (2125 of 2125 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/nl/

Translated using Weblate (Dutch)

Currently translated at 100.0% (56 of 56 strings)

Translation: Habitica/Faq
Translate-URL: https://translate.habitica.com/projects/habitica/faq/nl/
2020-08-11 05:10:44 +02:00
Matteo Pagliazzi
d82213bfee 4.151.5 2020-08-10 19:09:05 +02:00
Matteo Pagliazzi
535aa860f1 fix(task): make sure daysOfMonth update is only applied to dailies 2020-08-10 19:08:36 +02:00
Scott
5d169d477a Added bannedWordsAllowed attribute to Group attributes (Partial fix for #12405) (#12440)
* Added bannedWordsAllowed attribute to Group attributes

Added an optional Boolean attribute which will determine if banned words are allowed

* Added bannedWordsAllowed attribute to non-settable fields
2020-08-10 18:38:56 +02:00
Matteo Pagliazzi
d7ee1ec4f4 remove modals stack (#12423) 2020-08-10 18:38:24 +02:00
Sabe Jones
8c3a9c6dbc fix(teams): smol tweaks (#12443) 2020-08-10 11:27:19 -05:00
Matteo Pagliazzi
747c4ffbad Revert "fix(package-lock.json): rebuild"
This reverts commit e30e2f23ac.
2020-08-10 16:51:21 +02:00
Matteo Pagliazzi
e30e2f23ac fix(package-lock.json): rebuild 2020-08-10 16:25:08 +02:00
Matteo Pagliazzi
d016c1fa0a Revert "build(deps): bump mongoose from 5.9.27 to 5.9.28 (#12449)" (#12458)
This reverts commit 8796dbd8b8.
2020-08-10 16:20:29 +02:00
dependabot-preview[bot]
5b2b51e4f6 build(deps): bump superagent from 5.3.1 to 6.0.0 (#12450)
Bumps [superagent](https://github.com/visionmedia/superagent) from 5.3.1 to 6.0.0.
- [Release notes](https://github.com/visionmedia/superagent/releases)
- [Changelog](https://github.com/visionmedia/superagent/blob/master/HISTORY.md)
- [Commits](https://github.com/visionmedia/superagent/compare/v5.3.1...v6.0.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-08-10 15:31:26 +02:00
dependabot-preview[bot]
7393ef5162 build(deps): bump vue-router from 3.3.4 to 3.4.2 in /website/client (#12457)
Bumps [vue-router](https://github.com/vuejs/vue-router) from 3.3.4 to 3.4.2.
- [Release notes](https://github.com/vuejs/vue-router/releases)
- [Changelog](https://github.com/vuejs/vue-router/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/vuejs/vue-router/compare/v3.3.4...v3.4.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-08-10 15:29:58 +02:00
dependabot-preview[bot]
8a548a6a4d build(deps): bump bootstrap from 4.5.0 to 4.5.2 in /website/client (#12456)
Bumps [bootstrap](https://github.com/twbs/bootstrap) from 4.5.0 to 4.5.2.
- [Release notes](https://github.com/twbs/bootstrap/releases)
- [Commits](https://github.com/twbs/bootstrap/compare/v4.5.0...v4.5.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-08-10 15:29:36 +02:00
dependabot-preview[bot]
5ed007190a build(deps): bump got from 11.5.1 to 11.5.2 (#12454)
Bumps [got](https://github.com/sindresorhus/got) from 11.5.1 to 11.5.2.
- [Release notes](https://github.com/sindresorhus/got/releases)
- [Commits](https://github.com/sindresorhus/got/compare/v11.5.1...v11.5.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-08-10 15:28:41 +02:00
dependabot-preview[bot]
24afffc2ae build(deps): bump @babel/core from 7.11.0 to 7.11.1 (#12452)
Bumps [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core) from 7.11.0 to 7.11.1.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.11.1/packages/babel-core)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-08-10 15:28:31 +02:00
dependabot-preview[bot]
2ff3c7326c build(deps): bump csv-stringify from 5.5.0 to 5.5.1 (#12451)
Bumps [csv-stringify](https://github.com/adaltas/node-csv-stringify) from 5.5.0 to 5.5.1.
- [Release notes](https://github.com/adaltas/node-csv-stringify/releases)
- [Changelog](https://github.com/adaltas/node-csv-stringify/blob/master/CHANGELOG.md)
- [Commits](https://github.com/adaltas/node-csv-stringify/compare/v5.5.0...v5.5.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-08-10 15:28:07 +02:00
dependabot-preview[bot]
8796dbd8b8 build(deps): bump mongoose from 5.9.27 to 5.9.28 (#12449)
Bumps [mongoose](https://github.com/Automattic/mongoose) from 5.9.27 to 5.9.28.
- [Release notes](https://github.com/Automattic/mongoose/releases)
- [Changelog](https://github.com/Automattic/mongoose/blob/master/History.md)
- [Commits](https://github.com/Automattic/mongoose/compare/5.9.27...5.9.28)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-08-10 15:25:31 +02:00
negue
d2fc7c0c3d UI: redesign DatePicker (#12418)
* extract datepicker & settings as component + redesign

* fix updating values + fix button styling + popover position

* remove eslint-config-standard / html
2020-08-09 22:25:56 +02:00
Carlton McFarlane
13a5b276e9 fix(quest shop): add an index to v-for loop (#12446) 2020-08-09 19:58:40 +02:00
Rogesson
0a47af4ac3 update group members after accepting the quest (without page reload) (#12358)
* update group members after accepting the quest (without page reload)

* show quest information after starting

* removing quest.progress.up assignment

Co-authored-by: rogesson <rogesson.barboza@locaweb.com.br>
2020-08-09 19:44:46 +02:00
Jalansh
c0bf2cffea Casting Chilling Frost and Stealth skill again will not be processed and return an error instead. Fixes #12361. (#12404)
* Added logic for a repeating Chilling Frost skill. Added test case for redundant chilling frost skill cast. Added comments for the logic of repeating Stealth skill because of an error.

* Added logic for a repeating Stealth skill. Avoiding MP reduction still pending because of console error. Test cases pending.

* Completed the logic for a repeated Stealth skill. Added repeated frost skill cast check in common. Removed exclusive test. Test cases are pending.

* Added test case for Stealth skill recast. Fixed lint errors. Fixed a flaw in if statement which led to test case failure.

* Fixed lint errors in test case.

* Added a common JSON entry for skil recasts in three files. Other files remaining. Added Chilling Frost recast check in common code. Modified test cases.

* Added spellDisabled condition in client code.

* Reverted JSON messages for three languages. Added spellAlreadyCast attribute to JSON file in locales/en. Made changes for showing appropriate message in client code.

* Added an import for throwing BadRequest in common code. Modified test case accordingly.

* Update website/common/script/content/spells.js

Co-authored-by: Matteo Pagliazzi <matteopagliazzi@gmail.com>

* Added target and req attributes in cast() method arguments.

* Changed common code test case because of increased function parameters. Moved chilling frost test casse to common tests instead of server tests.

* Changed the test case format in common tests.

* Added a missing done statement.

* Fixed a minor error which led to failing test case. Removed the exclusive test which led to lint error.

* Fixed lint errors.

* Added a class named 'disabled' for the frontend change.

* fix(skills): style cleanup

* fix(skills): unfix

Co-authored-by: Matteo Pagliazzi <matteopagliazzi@gmail.com>
Co-authored-by: Sabe Jones <sabrecat@gmail.com>
2020-08-09 18:25:59 +02:00
Sabe Jones
954040dff8 4.151.4 2020-08-07 15:22:50 -05:00
Sabe Jones
e7af07cebb Merge branch 'develop' into release 2020-08-07 15:22:43 -05:00
Sabe Jones
d7d7f82723 fix(lint): allow console logging during Gulp tasks 2020-08-04 11:44:20 -05:00
Sabe Jones
7daaf04d0d fix(event): client Gala reversions 2020-08-03 13:50:30 -05:00
Melior
dc8c40c613 Translated using Weblate (Dutch)
Currently translated at 99.9% (2124 of 2125 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/nl/

Merge branch 'origin/develop' into Weblate.

Translated using Weblate (Dutch)

Currently translated at 99.9% (2123 of 2125 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/nl/

Translated using Weblate (Dutch)

Currently translated at 98.0% (2084 of 2125 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/nl/

Translated using Weblate (Dutch)

Currently translated at 100.0% (216 of 216 strings)

Translation: Habitica/Tasks
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/nl/

Translated using Weblate (Dutch)

Currently translated at 100.0% (216 of 216 strings)

Translation: Habitica/Tasks
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/nl/

Translated using Weblate (Dutch)

Currently translated at 97.7% (2077 of 2125 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/nl/

Translated using Weblate (Dutch)

Currently translated at 100.0% (216 of 216 strings)

Translation: Habitica/Tasks
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/nl/

Translated using Weblate (Dutch)

Currently translated at 100.0% (216 of 216 strings)

Translation: Habitica/Tasks
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/nl/

Translated using Weblate (Dutch)

Currently translated at 93.0% (201 of 216 strings)

Translation: Habitica/Tasks
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/nl/

Translated using Weblate (Spanish)

Currently translated at 88.9% (1891 of 2125 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/es/

Translated using Weblate (Czech)

Currently translated at 85.4% (1815 of 2125 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/cs/

Translated using Weblate (Czech)

Currently translated at 83.6% (210 of 251 strings)

Translation: Habitica/Subscriber
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/cs/

Translated using Weblate (Czech)

Currently translated at 100.0% (146 of 146 strings)

Translation: Habitica/Pets
Translate-URL: https://translate.habitica.com/projects/habitica/pets/cs/

Translated using Weblate (Czech)

Currently translated at 100.0% (500 of 500 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/cs/

Translated using Weblate (Czech)

Currently translated at 100.0% (212 of 212 strings)

Translation: Habitica/Settings
Translate-URL: https://translate.habitica.com/projects/habitica/settings/cs/

Translated using Weblate (Japanese)

Currently translated at 100.0% (251 of 251 strings)

Translation: Habitica/Subscriber
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/ja/

Translated using Weblate (Japanese)

Currently translated at 89.9% (1911 of 2125 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ja/

Translated using Weblate (Japanese)

Currently translated at 100.0% (251 of 251 strings)

Translation: Habitica/Subscriber
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/ja/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (2125 of 2125 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/zh_Hans/

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (328 of 328 strings)

Translation: Habitica/Front
Translate-URL: https://translate.habitica.com/projects/habitica/front/pt_BR/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (702 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/zh_Hans/

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (360 of 360 strings)

Translation: Habitica/Content
Translate-URL: https://translate.habitica.com/projects/habitica/content/pt_BR/

Translated using Weblate (French)

Currently translated at 100.0% (702 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/fr/

Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (2125 of 2125 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/zh_Hant/

Translated using Weblate (Czech)

Currently translated at 84.8% (1803 of 2125 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/cs/

Translated using Weblate (Czech)

Currently translated at 100.0% (124 of 124 strings)

Translation: Habitica/Communityguidelines
Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/cs/

Translated using Weblate (Japanese)

Currently translated at 100.0% (216 of 216 strings)

Translation: Habitica/Tasks
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/ja/

Translated using Weblate (Japanese)

Currently translated at 89.5% (1903 of 2125 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ja/

Translated using Weblate (Japanese)

Currently translated at 100.0% (86 of 86 strings)

Translation: Habitica/Achievements
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/ja/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (2125 of 2125 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/zh_Hans/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (702 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/zh_Hans/

Translated using Weblate (Japanese)

Currently translated at 97.1% (682 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/ja/
2020-08-03 20:41:36 +02:00
Sabe Jones
9a0e029491 Merge branch 'release' into develop 2020-08-03 13:27:57 -05:00
Sabe Jones
d6cfbd5529 4.151.3 2020-08-03 13:27:13 -05:00
Sabe Jones
74244fd3ba chore(event): Splash cleanup and Bailey 2020-08-03 13:26:53 -05:00
Matteo Pagliazzi
65e15e0c1d Fix multiple purchases with Amazon Pay (#12422)
* fix(amazon): logout when modal is closed, prevents issue with multiple order reference ids

* amazon logout when the button is loaded the first time
2020-08-03 15:34:18 +02:00
dependabot-preview[bot]
d21b9a5af4 build(deps): bump @babel/preset-env from 7.10.4 to 7.11.0 (#12433)
Bumps [@babel/preset-env](https://github.com/babel/babel/tree/HEAD/packages/babel-preset-env) from 7.10.4 to 7.11.0.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.11.0/packages/babel-preset-env)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
Co-authored-by: Matteo Pagliazzi <matteopagliazzi@gmail.com>
2020-08-03 12:42:42 +02:00
dependabot-preview[bot]
7cedecf27e build(deps): bump uuid from 8.2.0 to 8.3.0 in /website/client (#12434)
Bumps [uuid](https://github.com/uuidjs/uuid) from 8.2.0 to 8.3.0.
- [Release notes](https://github.com/uuidjs/uuid/releases)
- [Changelog](https://github.com/uuidjs/uuid/blob/master/CHANGELOG.md)
- [Commits](https://github.com/uuidjs/uuid/compare/v8.2.0...v8.3.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-08-03 12:41:05 +02:00
dependabot-preview[bot]
9aaeb2c4ac build(deps): bump @babel/core from 7.10.5 to 7.11.0 (#12428)
Bumps [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core) from 7.10.5 to 7.11.0.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.11.0/packages/babel-core)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-08-03 12:40:13 +02:00
dependabot-preview[bot]
df461f7642 build(deps): bump webpack from 4.44.0 to 4.44.1 in /website/client (#12435)
Bumps [webpack](https://github.com/webpack/webpack) from 4.44.0 to 4.44.1.
- [Release notes](https://github.com/webpack/webpack/releases)
- [Commits](https://github.com/webpack/webpack/compare/v4.44.0...v4.44.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-08-03 12:40:02 +02:00
dependabot-preview[bot]
0f72064923 build(deps-dev): bump monk from 7.3.0 to 7.3.1 (#12425)
Bumps [monk](https://github.com/Automattic/monk) from 7.3.0 to 7.3.1.
- [Release notes](https://github.com/Automattic/monk/releases)
- [Changelog](https://github.com/Automattic/monk/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Automattic/monk/compare/v7.3.0...v7.3.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-08-03 12:39:39 +02:00
dependabot-preview[bot]
139645ff76 build(deps): bump mongoose from 5.9.25 to 5.9.27 (#12426)
Bumps [mongoose](https://github.com/Automattic/mongoose) from 5.9.25 to 5.9.27.
- [Release notes](https://github.com/Automattic/mongoose/releases)
- [Changelog](https://github.com/Automattic/mongoose/blob/master/History.md)
- [Commits](https://github.com/Automattic/mongoose/compare/5.9.25...5.9.27)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-08-03 12:39:30 +02:00
dependabot-preview[bot]
aedcb483a0 build(deps): bump rate-limiter-flexible from 2.1.9 to 2.1.10 (#12429)
Bumps [rate-limiter-flexible](https://github.com/animir/node-rate-limiter-flexible) from 2.1.9 to 2.1.10.
- [Release notes](https://github.com/animir/node-rate-limiter-flexible/releases)
- [Commits](https://github.com/animir/node-rate-limiter-flexible/commits/v2.1.10)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-08-03 12:38:41 +02:00
dependabot-preview[bot]
cfb335ab78 build(deps): bump uuid from 8.2.0 to 8.3.0 (#12430)
Bumps [uuid](https://github.com/uuidjs/uuid) from 8.2.0 to 8.3.0.
- [Release notes](https://github.com/uuidjs/uuid/releases)
- [Changelog](https://github.com/uuidjs/uuid/blob/master/CHANGELOG.md)
- [Commits](https://github.com/uuidjs/uuid/compare/v8.2.0...v8.3.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-08-03 12:38:20 +02:00
dependabot-preview[bot]
70aea8c14a build(deps): bump bootstrap-vue from 2.15.0 to 2.16.0 in /website/client (#12436)
Bumps [bootstrap-vue](https://github.com/bootstrap-vue/bootstrap-vue) from 2.15.0 to 2.16.0.
- [Release notes](https://github.com/bootstrap-vue/bootstrap-vue/releases)
- [Changelog](https://github.com/bootstrap-vue/bootstrap-vue/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/bootstrap-vue/bootstrap-vue/compare/v2.15.0...v2.16.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-08-03 12:37:25 +02:00
Matteo Pagliazzi
f53022c00e fix(monthly tests): update test to use future date 2020-08-02 16:29:26 +02:00
Melior
b82a79361b Merge branch 'origin/develop' into Weblate. 2020-07-31 21:53:24 +02:00
Sabe Jones
ebb3a12e92 fix(migrations): better dummy require 2020-07-31 14:52:23 -05:00
Sabe Jones
272a6ec19d fix(migrations): better dummy require 2020-07-31 14:52:08 -05:00
Melior
dbf2ee6d6d Merge branch 'origin/develop' into Weblate. 2020-07-31 21:50:50 +02:00
Melior
af0b2ab16e Translated using Weblate (Hindi)
Currently translated at 0.6% (2 of 328 strings)

Translation: Habitica/Front
Translate-URL: https://translate.habitica.com/projects/habitica/front/hi/

Translated using Weblate (Czech)

Currently translated at 100.0% (212 of 212 strings)

Translation: Habitica/Settings
Translate-URL: https://translate.habitica.com/projects/habitica/settings/cs/

Translated using Weblate (Czech)

Currently translated at 83.8% (1781 of 2125 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/cs/

Translated using Weblate (Czech)

Currently translated at 100.0% (124 of 124 strings)

Translation: Habitica/Communityguidelines
Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/cs/

Translated using Weblate (Czech)

Currently translated at 100.0% (212 of 212 strings)

Translation: Habitica/Settings
Translate-URL: https://translate.habitica.com/projects/habitica/settings/cs/

Translated using Weblate (Polish)

Currently translated at 81.3% (1728 of 2125 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/pl/

Translated using Weblate (Polish)

Currently translated at 83.4% (586 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/pl/

Translated using Weblate (Japanese)

Currently translated at 99.8% (499 of 500 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/ja/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (702 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/zh_Hans/
2020-07-31 21:50:41 +02:00
Sabe Jones
43fb747c8f Merge branch 'release' into develop 2020-07-31 14:45:36 -05:00
Sabe Jones
9291414f7b 4.151.2 2020-07-31 14:45:01 -05:00
Sabe Jones
e4c95275ac chore(event): Naming Day news and migration 2020-07-31 14:38:41 -05:00
Matteo Pagliazzi
bd3e783274 Paypal IPN Upgrade (#12415)
* upgrade paypal ipn verification module

* paypal ipn: allow sandbox when testing

* improved error handling
2020-07-31 12:32:24 +02:00
Matteo Pagliazzi
9089b64af3 Merge pull request #12420 from HabitRPG/bigsee-12271
fix(task list): update todo due date on every save
2020-07-31 11:48:10 +02:00
Carlton
59f4e7f598 fix (task list) replace deprecated cache-busting of computed properties with methods 2020-07-31 11:41:00 +02:00
Laurel Thomson
1f8e2d5677 Updating daysOfMonths array when the startDate of a monthly is updated in the API - Fixes #12041 (#12399)
* Updating daily daysOfMonth array when startDate is updated and adding integration tests to test this functionality

* Adding more test cases and adding semicolons to the end of lines

* Only updating the monthly repetition if the daily was already set to repeat on a day of the month

* fix(lint): whitespace

Co-authored-by: Laurel Thomson <laurel.beth.thomson@gmai.com>
Co-authored-by: Sabe Jones <sabrecat@gmail.com>
2020-07-31 11:16:55 +02:00
Ieahleen
c4049608a8 removing wrong info on updating challenge leader from apidoc (#12413)
* removing wrong info on updating challenge leader

as discussed in the Aspiring Comrades, doing the change because the route `/api/v3/challenges/:challengeId` can't be used to change challenge leader, so I am deleting it from the docs

* adding summary to section title

as requested by @paglias

* missing comma

* fix(lint): line length

Co-authored-by: Sabe Jones <sabrecat@gmail.com>
2020-07-31 11:10:47 +02:00
Sabe Jones
8003632041 fix(modals): remove unnecessary scrollbar (#12416)
Fixes #9863
2020-07-30 15:01:18 -05:00
Sabe Jones
f4c840faec chore(ABtest): end drop experiment in favor of boosting 2020-07-30 14:53:50 -05:00
Melior
e9bb171e04 Merge branch 'origin/develop' into Weblate. 2020-07-30 21:49:39 +02:00
Sabe Jones
13de119bbb 4.151.1 2020-07-30 14:41:34 -05:00
Sabe Jones
f27ece49d1 chore(news): Last Chance Bailey 2020-07-30 14:38:19 -05:00
Sabe Jones
fa98b724a9 Merge branch 'develop' into release 2020-07-30 14:23:04 -05:00
Melior
093ef68f5c Translated using Weblate (Japanese)
Currently translated at 100.0% (216 of 216 strings)

Translation: Habitica/Tasks
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/ja/

Translated using Weblate (Czech)

Currently translated at 100.0% (500 of 500 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/cs/

Translated using Weblate (Czech)

Currently translated at 100.0% (298 of 298 strings)

Translation: Habitica/Generic
Translate-URL: https://translate.habitica.com/projects/habitica/generic/cs/

Translated using Weblate (Spanish)

Currently translated at 97.7% (531 of 543 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/es/

Translated using Weblate (Spanish)

Currently translated at 100.0% (86 of 86 strings)

Translation: Habitica/Achievements
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/es/

Translated using Weblate (Czech)

Currently translated at 100.0% (328 of 328 strings)

Translation: Habitica/Front
Translate-URL: https://translate.habitica.com/projects/habitica/front/cs/

Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (216 of 216 strings)

Translation: Habitica/Tasks
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/zh_Hant/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (216 of 216 strings)

Translation: Habitica/Tasks
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/zh_Hans/

Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (251 of 251 strings)

Translation: Habitica/Subscriber
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/zh_Hant/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (251 of 251 strings)

Translation: Habitica/Subscriber
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/zh_Hans/

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (216 of 216 strings)

Translation: Habitica/Tasks
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/en_GB/

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (251 of 251 strings)

Translation: Habitica/Subscriber
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/en_GB/

Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (500 of 500 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/zh_Hant/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (500 of 500 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/zh_Hans/

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (500 of 500 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/en_GB/

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (2125 of 2125 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/en_GB/

Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (86 of 86 strings)

Translation: Habitica/Achievements
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/zh_Hant/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (86 of 86 strings)

Translation: Habitica/Achievements
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/zh_Hans/

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (86 of 86 strings)

Translation: Habitica/Achievements
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/en_GB/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (702 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/zh_Hans/

Translated using Weblate (Italian)

Currently translated at 100.0% (2125 of 2125 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (702 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/it/

Translated using Weblate (Russian)

Currently translated at 99.5% (215 of 216 strings)

Translation: Habitica/Tasks
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/ru/

Translated using Weblate (Russian)

Currently translated at 98.6% (213 of 216 strings)

Translation: Habitica/Tasks
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/ru/

Translated using Weblate (Italian)

Currently translated at 100.0% (2125 of 2125 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/it/

Translated using Weblate (French)

Currently translated at 100.0% (2125 of 2125 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/fr/

Translated using Weblate (Czech)

Currently translated at 92.1% (129 of 140 strings)

Translation: Habitica/Quests
Translate-URL: https://translate.habitica.com/projects/habitica/quests/cs/

Translated using Weblate (Czech)

Currently translated at 89.2% (125 of 140 strings)

Translation: Habitica/Quests
Translate-URL: https://translate.habitica.com/projects/habitica/quests/cs/

Translated using Weblate (Czech)

Currently translated at 100.0% (124 of 124 strings)

Translation: Habitica/Communityguidelines
Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/cs/

Translated using Weblate (Italian)

Currently translated at 100.0% (216 of 216 strings)

Translation: Habitica/Tasks
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (251 of 251 strings)

Translation: Habitica/Subscriber
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (2125 of 2125 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/it/

Translated using Weblate (Czech)

Currently translated at 83.8% (1781 of 2125 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/cs/

Translated using Weblate (Czech)

Currently translated at 97.5% (121 of 124 strings)

Translation: Habitica/Communityguidelines
Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/cs/

Translated using Weblate (Italian)

Currently translated at 100.0% (86 of 86 strings)

Translation: Habitica/Achievements
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/it/

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (216 of 216 strings)

Translation: Habitica/Tasks
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/pt_BR/

Translated using Weblate (French)

Currently translated at 100.0% (216 of 216 strings)

Translation: Habitica/Tasks
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/fr/

Translated using Weblate (German)

Currently translated at 100.0% (216 of 216 strings)

Translation: Habitica/Tasks
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/de/

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (251 of 251 strings)

Translation: Habitica/Subscriber
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/pt_BR/

Translated using Weblate (French)

Currently translated at 100.0% (251 of 251 strings)

Translation: Habitica/Subscriber
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/fr/

Translated using Weblate (German)

Currently translated at 100.0% (8 of 8 strings)

Translation: Habitica/Overview
Translate-URL: https://translate.habitica.com/projects/habitica/overview/de/

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (500 of 500 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/pt_BR/

Translated using Weblate (French)

Currently translated at 100.0% (500 of 500 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/fr/

Translated using Weblate (German)

Currently translated at 100.0% (500 of 500 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/de/

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (2125 of 2125 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/pt_BR/

Translated using Weblate (French)

Currently translated at 100.0% (2125 of 2125 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/fr/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (702 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/zh_Hans/

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (86 of 86 strings)

Translation: Habitica/Achievements
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/pt_BR/

Translated using Weblate (French)

Currently translated at 100.0% (86 of 86 strings)

Translation: Habitica/Achievements
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/fr/

Translated using Weblate (German)

Currently translated at 100.0% (86 of 86 strings)

Translation: Habitica/Achievements
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/de/

Translated using Weblate (Czech)

Currently translated at 100.0% (86 of 86 strings)

Translation: Habitica/Achievements
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/cs/

Translated using Weblate (German)

Currently translated at 100.0% (251 of 251 strings)

Translation: Habitica/Subscriber
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/de/

Translated using Weblate (German)

Currently translated at 98.6% (493 of 500 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/de/
2020-07-30 18:12:59 +02:00
Melior
a501b2a5a6 Merge branch 'origin/develop' into Weblate. 2020-07-28 17:58:30 +02:00
Melior
011d22330f Translated using Weblate (Hindi)
Currently translated at 96.5% (83 of 86 strings)

Translation: Habitica/Achievements
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/hi/

Translated using Weblate (German)

Currently translated at 100.0% (216 of 216 strings)

Translation: Habitica/Tasks
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/de/

Translated using Weblate (German)

Currently translated at 100.0% (2125 of 2125 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/de/

Translated using Weblate (Russian)

Currently translated at 100.0% (86 of 86 strings)

Translation: Habitica/Achievements
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/ru/

Translated using Weblate (German)

Currently translated at 100.0% (86 of 86 strings)

Translation: Habitica/Achievements
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/de/

Merge branch 'origin/develop' into Weblate.

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (173 of 173 strings)

Translation: Habitica/Npc
Translate-URL: https://translate.habitica.com/projects/habitica/npc/vi/

Translated using Weblate (Vietnamese)

Currently translated at 90.7% (127 of 140 strings)

Translation: Habitica/Quests
Translate-URL: https://translate.habitica.com/projects/habitica/quests/vi/

Translated using Weblate (Czech)

Currently translated at 100.0% (31 of 31 strings)

Translation: Habitica/Maintenance
Translate-URL: https://translate.habitica.com/projects/habitica/maintenance/cs/

Translated using Weblate (Czech)

Currently translated at 100.0% (185 of 185 strings)

Translation: Habitica/Limited
Translate-URL: https://translate.habitica.com/projects/habitica/limited/cs/

Translated using Weblate (Japanese)

Currently translated at 89.8% (1903 of 2119 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ja/

Translated using Weblate (Italian)

Currently translated at 100.0% (702 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (230 of 230 strings)

Translation: Habitica/Character
Translate-URL: https://translate.habitica.com/projects/habitica/character/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (702 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (543 of 543 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (493 of 493 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (328 of 328 strings)

Translation: Habitica/Front
Translate-URL: https://translate.habitica.com/projects/habitica/front/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (212 of 212 strings)

Translation: Habitica/Settings
Translate-URL: https://translate.habitica.com/projects/habitica/settings/it/

Translated using Weblate (Russian)

Currently translated at 100.0% (493 of 493 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/ru/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (702 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/zh_Hans/

Translated using Weblate (Italian)

Currently translated at 100.0% (212 of 212 strings)

Translation: Habitica/Settings
Translate-URL: https://translate.habitica.com/projects/habitica/settings/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (83 of 83 strings)

Translation: Habitica/Achievements
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (212 of 212 strings)

Translation: Habitica/Settings
Translate-URL: https://translate.habitica.com/projects/habitica/settings/it/

Translated using Weblate (Czech)

Currently translated at 100.0% (146 of 146 strings)

Translation: Habitica/Pets
Translate-URL: https://translate.habitica.com/projects/habitica/pets/cs/

Translated using Weblate (Italian)

Currently translated at 100.0% (2119 of 2119 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (702 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/it/

Translated using Weblate (Japanese)

Currently translated at 89.7% (1901 of 2119 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ja/

Translated using Weblate (Czech)

Currently translated at 84.0% (1780 of 2119 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/cs/

Translated using Weblate (Czech)

Currently translated at 100.0% (83 of 83 strings)

Translation: Habitica/Achievements
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/cs/

Translated using Weblate (Italian)

Currently translated at 100.0% (212 of 212 strings)

Translation: Habitica/Settings
Translate-URL: https://translate.habitica.com/projects/habitica/settings/it/

Translated using Weblate (Portuguese)

Currently translated at 82.0% (1739 of 2119 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/pt/

Translated using Weblate (Portuguese)

Currently translated at 81.8% (1734 of 2119 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/pt/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (702 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/zh_Hans/

Translated using Weblate (Czech)

Currently translated at 96.7% (120 of 124 strings)

Translation: Habitica/Communityguidelines
Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/cs/

Translated using Weblate (Czech)

Currently translated at 96.7% (120 of 124 strings)

Translation: Habitica/Communityguidelines
Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/cs/

Translated using Weblate (Russian)

Currently translated at 99.2% (539 of 543 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/ru/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (360 of 360 strings)

Translation: Habitica/Content
Translate-URL: https://translate.habitica.com/projects/habitica/content/zh_Hans/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (230 of 230 strings)

Translation: Habitica/Character
Translate-URL: https://translate.habitica.com/projects/habitica/character/zh_Hans/

Translated using Weblate (Japanese)

Currently translated at 89.5% (1897 of 2119 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ja/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (702 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/zh_Hans/

Translated using Weblate (Japanese)

Currently translated at 89.2% (1891 of 2119 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ja/

Translated using Weblate (Czech)

Currently translated at 100.0% (493 of 493 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/cs/

Translated using Weblate (Spanish)

Currently translated at 97.4% (529 of 543 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/es/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (702 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/zh_Hans/

Translated using Weblate (Czech)

Currently translated at 100.0% (493 of 493 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/cs/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (702 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/zh_Hans/

Translated using Weblate (Russian)

Currently translated at 99.0% (538 of 543 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/ru/

Translated using Weblate (Czech)

Currently translated at 100.0% (493 of 493 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/cs/

Translated using Weblate (French)

Currently translated at 100.0% (124 of 124 strings)

Translation: Habitica/Communityguidelines
Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/fr/

Translated using Weblate (French)

Currently translated at 100.0% (212 of 212 strings)

Translation: Habitica/Settings
Translate-URL: https://translate.habitica.com/projects/habitica/settings/fr/
2020-07-28 17:58:25 +02:00
Sabe Jones
cfb4acad1a fix(strings): missing set label 2020-07-28 10:07:10 -05:00
Sabe Jones
a7bbdf1cd3 fix(strings): missing set label 2020-07-28 10:06:47 -05:00
Sabe Jones
7546733ae9 Merge branch 'release' into develop 2020-07-28 10:01:31 -05:00
Sabe Jones
9490159f64 4.151.0 2020-07-28 10:00:20 -05:00
Sabe Jones
da5bb795ca chore(sprites): compile 2020-07-28 10:00:01 -05:00
Sabe Jones
26869e9006 feat(content): August 2020 subscriber set 2020-07-28 09:59:50 -05:00
Sabe Jones
11018156c5 fix(news): spritename 2020-07-27 14:28:12 -05:00
Sabe Jones
7280c50963 feat(content): Freshwater Friends cheevo 2020-07-27 14:17:43 -05:00
Matteo Pagliazzi
35a6f4cb19 docs(members): update members api docs to include info about includeAllPublicFields query parameter 2020-07-27 11:01:29 +02:00
dependabot-preview[bot]
4b5500b13d build(deps): bump webpack from 4.43.0 to 4.44.0 in /website/client (#12409)
Bumps [webpack](https://github.com/webpack/webpack) from 4.43.0 to 4.44.0.
- [Release notes](https://github.com/webpack/webpack/releases)
- [Commits](https://github.com/webpack/webpack/compare/v4.43.0...v4.44.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-07-27 10:09:16 +02:00
dependabot-preview[bot]
8b3a5ce6fe build(deps): bump regenerator-runtime from 0.13.5 to 0.13.7 (#12411)
Bumps [regenerator-runtime](https://github.com/facebook/regenerator) from 0.13.5 to 0.13.7.
- [Release notes](https://github.com/facebook/regenerator/releases)
- [Commits](https://github.com/facebook/regenerator/compare/regenerator-runtime@0.13.5...regenerator-runtime@0.13.7)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-07-27 10:08:38 +02:00
dependabot-preview[bot]
6a53cd29bf build(deps): bump apidoc from 0.23.0 to 0.24.0 (#12412)
Bumps [apidoc](https://github.com/apidoc/apidoc) from 0.23.0 to 0.24.0.
- [Release notes](https://github.com/apidoc/apidoc/releases)
- [Changelog](https://github.com/apidoc/apidoc/blob/master/CHANGELOG.md)
- [Commits](https://github.com/apidoc/apidoc/compare/0.23.0...0.24.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-07-27 10:08:26 +02:00
Matteo Pagliazzi
79c64763ac fix(lint): automatically fix lint warnings 2020-07-25 18:56:19 +02:00
negue
aaf32cc09b Teams UI Redesign and A11y Updates (#12142)
* WIP(a11y): task modal updates

* fix(tasks): borders in modal

* fix(tasks): circley locks

* fix(task-modal): placeholders

* WIP(task-modal): disabled states, hide empty options, +/- restyle

* fix(task-modal): box shadows instead of borders, habit control pointer

* fix(task-modal): button states?

* fix(modal): tighten up layout, new spacing utils

* fix(tasks): more stylin

* fix(tasks): habit hovers

* fix(css): checklist labels, a11y colors

* fix(css): one more missed hover issue

* fix(css): lock Challenges, label fixes

* fix(css): scope input/textarea changes

* fix(style): task tweakies

* fix(style): more button fixage

* WIP(component): start select list story

* working example of a templated selectList

* fix(style): more button corrections

* fix(lint): EOL

* fix(buttons): factor btn-secondary to better override Bootstrap

* fix(styles): standardize more buttons

* wip: difficulty select - style fixes

* selectDifficulty works! 🎉 - fix styles

* change the dropdown-item sizes only for the selectList ones

* selectTranslatedArray

* changed many label margins

* more correct dropdown style

* fix(modals): button corrections

* input-group styling + datetime picker without today button

* Style/margins for "repeat every" - extract selectTag.vue

* working tag-selection / update - cleanup

* fix stories

* fix svg color on create modal (purple)

* fix task modal bottom padding

* correct dropdown shadow

* update dropdown-toggle caret size / color

* fixed checklist style

* sync checked state

* selectTag padding

* fix spacing between positive/negative streak inputs

* toggle-checkbox + fix some spacings

* disable repeat-on when its a groupTask

* fix new checklist-item

* fix toggle-checkbox style - fix difficulty style

* fix checklist ui

* add tags label , when there arent any tags selected

* WORKING select-tag component 🎉

* fix taglist story

* show max 5 items in tag dropdown + "X more" label

* fix datetime clear button

* replace m-b-xs to mb-1 (bootstrap) - fix input-group-text style

* fix styles of advanced settings

* fix delete task styles

* always show grippy on hover of the item

* extract modal-text-input mixin + fix the borders/dropshadow

* fix(spacing): revert most to Bootstrap

* feat(checklists): make local copy of master checklist non-editable
also aggressively update checklists because they weren't syncing??

* fix(checklists): handle add/remove options better

* feat(teams): manager notes field

* fix select/dropdown styles

* input border + icon colors

* delete task underline color

* fix checklist "delete icon" vertical position

* selectTag fixes - normal open/close toggle working again - remove icon color

* fixing icons:

Trash can - Delete
Little X - Remove
Big X - Close
Block - Block

* fix taglist margins / icon sizes

* wip margin overview (in storybook)

* fix routerlink

* remove unused method

* new selectTag style + add markdown inside tagList + scrollable tag selection

* fix selectTag / selectList active border

* fix difficulty select (svg default color)

* fix input padding-left + fix reset habit streak fullwidth / padding + "repeat every" gray text (no border)

* feat(teams): improved approval request > approve > reward flow

* fix(tests): address failures

* fix(lint): oops only

* fix(tasks): short-circuit group related logic

* fix(tasks): more short circuiting

* fix(tasks): more lines, less lint

* fix(tasks): how do i keep missing these

* feat(teams): provide assigning user summary

* fix(teams): don't attempt to record assiging user if not supplied

* fix advanced-settings styling / margin

* fix merge + hide advanced streak settings when none enabled

* fix styles

* set Roboto font for advanced settings

* Add Challenge flag to the tag list

* add tag with enter, when no other tag is found

* fix styles + tag cancel button

* refactor footer / margin

* split repeat fields into option mt-3 groups

* button all the things

* fix(tasks): style updates
* no hover state for non-editable tasks on team board
* keep assign/claim footer on task after requesting approval
* disable more fields on user copy of team task, and remove hover states 
for them

* fix(tasks): functional revisions
* "Claim Rewards" instead of "x" in task approved notif
* Remove default transition supplied by Bootstrap, apply individually to 
some elements
* Delete individual tasks and related notifications when master task 
deleted from team board
* Manager notes now save when supplied at task initial creation
* Can no longer dismiss rewards from approved task by hitting Dismiss 
All

* fix(tasks): clean tasksOrder
also adjust related test expectation

* fix(tests): adjust integration expectations

* fix(test): ratzen fratzen only

* fix(teams): checklist, notes

* fix(teams): improve disabled states

* fix(teams): more style fixage

* BREAKING(teams): return 202 instead of 401 for approval request

* fix(teams): better taskboard sync
also re-re-fix checklist borders

* fix(tests): update expectations for breaking change

* refactor(task-modal): lockable label component

* refactor(teams): move task scoring to mixin

* fix(teams): style corrections

* fix(tasks): spacing and wording corrections

* fix(teams): don't bork manager notes

* fix(teams): assignment fix and more approval flow revisions

* WIP(teams): use tag dropdown control for assignment

* refactor(tasks): better spacing, generic multi select

* fix(tasks): various visual and behavior updates

* fix(tasks): incidental style tweaks

* fix(teams): standardize approval request response

* refactor(teams): correct test, use res.respond message param

* fix(storybook): renamed component

* fix(teams): age approval-required To Do's
Fixes #8730

* fix(teams): sync personal data as well as team on mixin sync

* fix(teams): hide unclaim button, not whole footer; fix switch focus

* fix(achievements): unrevert width fix

Co-authored-by: Sabe Jones <sabrecat@gmail.com>
2020-07-25 07:37:10 -05:00
Jake North
7ee6ff18ce Fix height of badges in multiline achievements (#12406)
This fixes the UI bug I reported where achievement names taking up multiple lines cause badges to stretch to fill their container.

Screenshot of bug: https://i.snipboard.io/07GBi4.jpg
Screenshot of fix: https://i.snipboard.io/PKvi8e.jpg
2020-07-25 13:46:06 +02:00
Bart Enkelaar
234258b41e Move from deprecated moment#zone to moment#utcOffset (#12207)
* Issue 10209 - Remove read usages of zone

* Issue 10209 - Add coverage on daysSince and startOfDay cron utility functions

* Issue 10209 - Add unit test for daysUserHasMissed method

* Issue 10209 - Remove usages of deprecated `moment.js#zone` method.

* Issue 10209 - Add helper function to centralise logic

Also simplify timezoneOffsetToUtc function in site.vue

* Issue 10209 - Also add getUtcOffset as method on user

Co-authored-by: Matteo Pagliazzi <matteopagliazzi@gmail.com>
2020-07-25 13:22:41 +02:00
dependabot-preview[bot]
c10b9b7993 build(deps): bump mongoose from 5.9.24 to 5.9.25 (#12402)
Bumps [mongoose](https://github.com/Automattic/mongoose) from 5.9.24 to 5.9.25.
- [Release notes](https://github.com/Automattic/mongoose/releases)
- [Changelog](https://github.com/Automattic/mongoose/blob/master/History.md)
- [Commits](https://github.com/Automattic/mongoose/compare/5.9.24...5.9.25)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-07-25 12:27:02 +02:00
Melior
0f945ee369 Merge branch 'origin/develop' into Weblate. 2020-07-23 17:53:19 +02:00
Melior
0c5bede1ed Translated using Weblate (Hindi)
Currently translated at 76.6% (416 of 543 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/hi/

Translated using Weblate (Hindi)

Currently translated at 98.7% (82 of 83 strings)

Translation: Habitica/Achievements
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/hi/

Translated using Weblate (Czech)

Currently translated at 100.0% (31 of 31 strings)

Translation: Habitica/Maintenance
Translate-URL: https://translate.habitica.com/projects/habitica/maintenance/cs/

Translated using Weblate (Polish)

Currently translated at 81.5% (1727 of 2119 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/pl/

Translated using Weblate (Czech)

Currently translated at 100.0% (212 of 212 strings)

Translation: Habitica/Settings
Translate-URL: https://translate.habitica.com/projects/habitica/settings/cs/

Translated using Weblate (Hindi)

Currently translated at 76.6% (416 of 543 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/hi/

Translated using Weblate (Hindi)

Currently translated at 76.6% (416 of 543 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/hi/

Translated using Weblate (Polish)

Currently translated at 100.0% (250 of 250 strings)

Translation: Habitica/Subscriber
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/pl/

Translated using Weblate (Polish)

Currently translated at 81.4% (1726 of 2119 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/pl/

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (543 of 543 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/vi/

Translated using Weblate (Vietnamese)

Currently translated at 90.7% (127 of 140 strings)

Translation: Habitica/Quests
Translate-URL: https://translate.habitica.com/projects/habitica/quests/vi/

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (124 of 124 strings)

Translation: Habitica/Communityguidelines
Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/vi/

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (212 of 212 strings)

Translation: Habitica/Settings
Translate-URL: https://translate.habitica.com/projects/habitica/settings/vi/

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (124 of 124 strings)

Translation: Habitica/Communityguidelines
Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/pt_BR/

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (212 of 212 strings)

Translation: Habitica/Settings
Translate-URL: https://translate.habitica.com/projects/habitica/settings/pt_BR/

Translated using Weblate (Czech)

Currently translated at 100.0% (173 of 173 strings)

Translation: Habitica/Npc
Translate-URL: https://translate.habitica.com/projects/habitica/npc/cs/

Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (124 of 124 strings)

Translation: Habitica/Communityguidelines
Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/zh_Hant/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (124 of 124 strings)

Translation: Habitica/Communityguidelines
Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/zh_Hans/

Translated using Weblate (German)

Currently translated at 100.0% (702 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/de/

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (124 of 124 strings)

Translation: Habitica/Communityguidelines
Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/en_GB/

Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (212 of 212 strings)

Translation: Habitica/Settings
Translate-URL: https://translate.habitica.com/projects/habitica/settings/zh_Hant/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (212 of 212 strings)

Translation: Habitica/Settings
Translate-URL: https://translate.habitica.com/projects/habitica/settings/zh_Hans/

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (212 of 212 strings)

Translation: Habitica/Settings
Translate-URL: https://translate.habitica.com/projects/habitica/settings/en_GB/

Translated using Weblate (Czech)

Currently translated at 94.2% (163 of 173 strings)

Translation: Habitica/Npc
Translate-URL: https://translate.habitica.com/projects/habitica/npc/cs/

Translated using Weblate (Polish)

Currently translated at 100.0% (360 of 360 strings)

Translation: Habitica/Content
Translate-URL: https://translate.habitica.com/projects/habitica/content/pl/

Translated using Weblate (Italian)

Currently translated at 100.0% (213 of 213 strings)

Translation: Habitica/Tasks
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/it/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (2119 of 2119 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/zh_Hans/

Translated using Weblate (Japanese)

Currently translated at 89.0% (1887 of 2119 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ja/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (702 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/zh_Hans/

Translated using Weblate (Japanese)

Currently translated at 100.0% (124 of 124 strings)

Translation: Habitica/Communityguidelines
Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/ja/

Translated using Weblate (Italian)

Currently translated at 100.0% (124 of 124 strings)

Translation: Habitica/Communityguidelines
Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (83 of 83 strings)

Translation: Habitica/Achievements
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/it/

Translated using Weblate (Japanese)

Currently translated at 100.0% (212 of 212 strings)

Translation: Habitica/Settings
Translate-URL: https://translate.habitica.com/projects/habitica/settings/ja/

Translated using Weblate (German)

Currently translated at 100.0% (124 of 124 strings)

Translation: Habitica/Communityguidelines
Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/de/

Translated using Weblate (German)

Currently translated at 100.0% (212 of 212 strings)

Translation: Habitica/Settings
Translate-URL: https://translate.habitica.com/projects/habitica/settings/de/
2020-07-23 17:53:04 +02:00
Sabe Jones
d8ab69b3c7 4.150.0 2020-07-23 10:44:49 -05:00
Sabe Jones
bea77e9520 Merge branch 'develop' into release 2020-07-23 10:44:40 -05:00
Sabe Jones
108201a465 chore(sprites): compile 2020-07-23 10:44:12 -05:00
Sabe Jones
32e51bd551 feat(content): add new Naming Day item
also Bailey
2020-07-23 10:44:00 -05:00
Melior
6e03e41271 Merge branch 'origin/develop' into Weblate. 2020-07-21 22:01:58 +02:00
Sabe Jones
8d9851a489 Merge branch 'release' into develop 2020-07-21 14:58:42 -05:00
Sabe Jones
ad60946eeb 4.149.3 2020-07-21 14:58:16 -05:00
Sabe Jones
c5e02292c4 feat(content): award yearly Orcas 2020-07-21 14:57:52 -05:00
Melior
e2d0ddfb39 Translated using Weblate (Vietnamese)
Currently translated at 100.0% (173 of 173 strings)

Translation: Habitica/Npc
Translate-URL: https://translate.habitica.com/projects/habitica/npc/vi/

Translated using Weblate (Czech)

Currently translated at 100.0% (213 of 213 strings)

Translation: Habitica/Tasks
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/cs/

Translated using Weblate (Czech)

Currently translated at 89.2% (125 of 140 strings)

Translation: Habitica/Quests
Translate-URL: https://translate.habitica.com/projects/habitica/quests/cs/

Translated using Weblate (Czech)

Currently translated at 98.9% (183 of 185 strings)

Translation: Habitica/Limited
Translate-URL: https://translate.habitica.com/projects/habitica/limited/cs/

Translated using Weblate (Czech)

Currently translated at 98.9% (183 of 185 strings)

Translation: Habitica/Limited
Translate-URL: https://translate.habitica.com/projects/habitica/limited/cs/

Translated using Weblate (Czech)

Currently translated at 98.9% (183 of 185 strings)

Translation: Habitica/Limited
Translate-URL: https://translate.habitica.com/projects/habitica/limited/cs/

Translated using Weblate (Czech)

Currently translated at 83.8% (1777 of 2119 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/cs/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (702 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/zh_Hans/

Translated using Weblate (Russian)

Currently translated at 100.0% (230 of 230 strings)

Translation: Habitica/Character
Translate-URL: https://translate.habitica.com/projects/habitica/character/ru/

Translated using Weblate (Polish)

Currently translated at 100.0% (185 of 185 strings)

Translation: Habitica/Limited
Translate-URL: https://translate.habitica.com/projects/habitica/limited/pl/

Translated using Weblate (Czech)

Currently translated at 92.4% (171 of 185 strings)

Translation: Habitica/Limited
Translate-URL: https://translate.habitica.com/projects/habitica/limited/cs/

Translated using Weblate (Polish)

Currently translated at 81.4% (1725 of 2119 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/pl/

Translated using Weblate (Japanese)

Currently translated at 88.8% (1883 of 2119 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ja/

Translated using Weblate (Polish)

Currently translated at 100.0% (56 of 56 strings)

Translation: Habitica/Faq
Translate-URL: https://translate.habitica.com/projects/habitica/faq/pl/

Translated using Weblate (Polish)

Currently translated at 100.0% (230 of 230 strings)

Translation: Habitica/Character
Translate-URL: https://translate.habitica.com/projects/habitica/character/pl/

Translated using Weblate (Polish)

Currently translated at 100.0% (185 of 185 strings)

Translation: Habitica/Limited
Translate-URL: https://translate.habitica.com/projects/habitica/limited/pl/

Translated using Weblate (Polish)

Currently translated at 100.0% (56 of 56 strings)

Translation: Habitica/Faq
Translate-URL: https://translate.habitica.com/projects/habitica/faq/pl/

Translated using Weblate (Polish)

Currently translated at 100.0% (140 of 140 strings)

Translation: Habitica/Quests
Translate-URL: https://translate.habitica.com/projects/habitica/quests/pl/

Translated using Weblate (Polish)

Currently translated at 100.0% (173 of 173 strings)

Translation: Habitica/Npc
Translate-URL: https://translate.habitica.com/projects/habitica/npc/pl/

Translated using Weblate (Polish)

Currently translated at 100.0% (83 of 83 strings)

Translation: Habitica/Achievements
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/pl/

Translated using Weblate (Latin)

Currently translated at 100.0% (83 of 83 strings)

Translation: Habitica/Achievements
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/la/

Translated using Weblate (Czech)

Currently translated at 83.8% (1776 of 2119 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/cs/

Translated using Weblate (Czech)

Currently translated at 100.0% (543 of 543 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/cs/

Translated using Weblate (Czech)

Currently translated at 100.0% (543 of 543 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/cs/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (2119 of 2119 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/zh_Hans/

Translated using Weblate (Polish)

Currently translated at 100.0% (250 of 250 strings)

Translation: Habitica/Subscriber
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/pl/

Translated using Weblate (Polish)

Currently translated at 100.0% (57 of 57 strings)

Translation: Habitica/Spells
Translate-URL: https://translate.habitica.com/projects/habitica/spells/pl/

Translated using Weblate (Polish)

Currently translated at 100.0% (28 of 28 strings)

Translation: Habitica/Rebirth
Translate-URL: https://translate.habitica.com/projects/habitica/rebirth/pl/

Translated using Weblate (Polish)

Currently translated at 100.0% (146 of 146 strings)

Translation: Habitica/Pets
Translate-URL: https://translate.habitica.com/projects/habitica/pets/pl/

Translated using Weblate (Polish)

Currently translated at 100.0% (8 of 8 strings)

Translation: Habitica/Overview
Translate-URL: https://translate.habitica.com/projects/habitica/overview/pl/

Translated using Weblate (Polish)

Currently translated at 100.0% (493 of 493 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/pl/

Translated using Weblate (Polish)

Currently translated at 100.0% (328 of 328 strings)

Translation: Habitica/Front
Translate-URL: https://translate.habitica.com/projects/habitica/front/pl/

Translated using Weblate (Polish)

Currently translated at 100.0% (63 of 63 strings)

Translation: Habitica/Defaulttasks
Translate-URL: https://translate.habitica.com/projects/habitica/defaulttasks/pl/

Translated using Weblate (Polish)

Currently translated at 100.0% (124 of 124 strings)

Translation: Habitica/Communityguidelines
Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/pl/

Translated using Weblate (Polish)

Currently translated at 100.0% (360 of 360 strings)

Translation: Habitica/Content
Translate-URL: https://translate.habitica.com/projects/habitica/content/pl/

Translated using Weblate (Polish)

Currently translated at 100.0% (211 of 211 strings)

Translation: Habitica/Settings
Translate-URL: https://translate.habitica.com/projects/habitica/settings/pl/

Translated using Weblate (Czech)

Currently translated at 95.9% (521 of 543 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/cs/

Translated using Weblate (Czech)

Currently translated at 94.1% (511 of 543 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/cs/

Translated using Weblate (Czech)

Currently translated at 78.3% (145 of 185 strings)

Translation: Habitica/Limited
Translate-URL: https://translate.habitica.com/projects/habitica/limited/cs/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (702 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/zh_Hans/

Translated using Weblate (Czech)

Currently translated at 93.7% (509 of 543 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/cs/

Translated using Weblate (Japanese)

Currently translated at 88.6% (1879 of 2119 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ja/

Translated using Weblate (Hindi)

Currently translated at 76.0% (413 of 543 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/hi/

Translated using Weblate (Hindi)

Currently translated at 100.0% (83 of 83 strings)

Translation: Habitica/Achievements
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/hi/

Translated using Weblate (Russian)

Currently translated at 100.0% (140 of 140 strings)

Translation: Habitica/Quests
Translate-URL: https://translate.habitica.com/projects/habitica/quests/ru/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (493 of 493 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/zh_Hans/

Translated using Weblate (Russian)

Currently translated at 100.0% (493 of 493 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/ru/

Translated using Weblate (Japanese)

Currently translated at 88.3% (1873 of 2119 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ja/

Translated using Weblate (German)

Currently translated at 100.0% (2119 of 2119 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/de/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (702 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/zh_Hans/

Translated using Weblate (Russian)

Currently translated at 99.2% (697 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/ru/

Translated using Weblate (German)

Currently translated at 100.0% (702 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/de/

Translated using Weblate (Hindi)

Currently translated at 100.0% (83 of 83 strings)

Translation: Habitica/Achievements
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/hi/

Translated using Weblate (French)

Currently translated at 100.0% (702 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/fr/

Translated using Weblate (Czech)

Currently translated at 92.2% (501 of 543 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/cs/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (83 of 83 strings)

Translation: Habitica/Achievements
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/zh_Hans/

Translated using Weblate (Italian)

Currently translated at 100.0% (146 of 146 strings)

Translation: Habitica/Pets
Translate-URL: https://translate.habitica.com/projects/habitica/pets/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (67 of 67 strings)

Translation: Habitica/Messages
Translate-URL: https://translate.habitica.com/projects/habitica/messages/it/

Translated using Weblate (Czech)

Currently translated at 100.0% (12 of 12 strings)

Translation: Habitica/Merch
Translate-URL: https://translate.habitica.com/projects/habitica/merch/cs/

Translated using Weblate (Italian)

Currently translated at 100.0% (31 of 31 strings)

Translation: Habitica/Maintenance
Translate-URL: https://translate.habitica.com/projects/habitica/maintenance/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (185 of 185 strings)

Translation: Habitica/Limited
Translate-URL: https://translate.habitica.com/projects/habitica/limited/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (2119 of 2119 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/it/

Translated using Weblate (German)

Currently translated at 99.2% (2104 of 2119 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/de/

Translated using Weblate (Italian)

Currently translated at 100.0% (328 of 328 strings)

Translation: Habitica/Front
Translate-URL: https://translate.habitica.com/projects/habitica/front/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (56 of 56 strings)

Translation: Habitica/Faq
Translate-URL: https://translate.habitica.com/projects/habitica/faq/it/

Translated using Weblate (Czech)

Currently translated at 100.0% (63 of 63 strings)

Translation: Habitica/Defaulttasks
Translate-URL: https://translate.habitica.com/projects/habitica/defaulttasks/cs/

Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (702 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/zh_Hant/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (702 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/zh_Hans/

Translated using Weblate (Italian)

Currently translated at 100.0% (702 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/it/

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (702 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/en_GB/

Translated using Weblate (Italian)

Currently translated at 100.0% (124 of 124 strings)

Translation: Habitica/Communityguidelines
Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/it/

Translated using Weblate (Czech)

Currently translated at 95.9% (119 of 124 strings)

Translation: Habitica/Communityguidelines
Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/cs/

Translated using Weblate (Czech)

Currently translated at 100.0% (360 of 360 strings)

Translation: Habitica/Content
Translate-URL: https://translate.habitica.com/projects/habitica/content/cs/

Translated using Weblate (Italian)

Currently translated at 100.0% (134 of 134 strings)

Translation: Habitica/Challenge
Translate-URL: https://translate.habitica.com/projects/habitica/challenge/it/

Translated using Weblate (Czech)

Currently translated at 90.2% (490 of 543 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/cs/

Translated using Weblate (Czech)

Currently translated at 100.0% (211 of 211 strings)

Translation: Habitica/Settings
Translate-URL: https://translate.habitica.com/projects/habitica/settings/cs/

Translated using Weblate (Spanish (Latin America))

Currently translated at 79.6% (199 of 250 strings)

Translation: Habitica/Subscriber
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/es_419/

Translated using Weblate (Spanish (Latin America))

Currently translated at 97.9% (532 of 543 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/es_419/

Translated using Weblate (Spanish (Latin America))

Currently translated at 100.0% (83 of 83 strings)

Translation: Habitica/Achievements
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/es_419/

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (702 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/pt_BR/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (230 of 230 strings)

Translation: Habitica/Character
Translate-URL: https://translate.habitica.com/projects/habitica/character/zh_Hans/
2020-07-21 19:35:39 +02:00
Matteo Pagliazzi
3db2cd49a4 fix(rate limit); more informative error message 2020-07-20 23:52:51 +02:00
Matteo Pagliazzi
02cac78896 fix(rate limit); more informative error message 2020-07-20 23:52:33 +02:00
Matteo Pagliazzi
fef9c74f9b Merge branch 'release' into develop 2020-07-19 18:26:48 +02:00
Matteo Pagliazzi
858a8749a4 4.149.2 2020-07-19 18:26:24 +02:00
Matteo Pagliazzi
fd7c5b3847 feat(members): allow to fetch up to 60 members at all (#12400) 2020-07-19 18:25:46 +02:00
Alys
73aa32ca31 change Community Guidelines to remove special instructions for reporting PMs
Private messages in your inbox can now be reported in the same way
as guild posts.
2020-07-19 20:47:15 +10:00
Amber
ead0b6c56f PR to fix: Disallow line breaks in display names (#12380)
* Update settings.json

* Update index.js

* Update validation.js

* Update validation.js

* Update validation.js

Removes the second check

* Update tests and validation

Added tests, and updated validation
2020-07-18 22:41:19 +02:00
Matteo Pagliazzi
e550ca1531 Merge branch 'release' into develop 2020-07-18 15:37:18 +02:00
Matteo Pagliazzi
88059f568c 4.149.1 2020-07-18 15:36:56 +02:00
Matteo Pagliazzi
9f85d3927f fix(content): include app version in response 2020-07-18 15:36:44 +02:00
Matteo Pagliazzi
d8badb6d12 4.149.0 2020-07-18 15:00:39 +02:00
Matteo Pagliazzi
f5e4e2150a fix(tests): remove exclusive unit test 2020-07-18 15:00:31 +02:00
Matteo Pagliazzi
6743dcb08a fix(cors): expose rate limit headers to clients 2020-07-18 15:00:23 +02:00
Matteo Pagliazzi
e7c8833c9a API v3 Rate Limiter (#12117)
* simplify ip address management by using the trust proxy express option

* add setupExpress file

* fix redirects middleware tests

* fix lint

* short circuit the ip blocking middleware

* basic implementation with ip based limiting

* improve logging

* upgrade apidoc

* apidoc: add introduction section

* fix lint

* fix tests

* fix lint

* add unit tests for rate limiter

* do not send retry-after header when points are available

* automatically fix lint

* fix more lint issues

* use userId as key for rate limit when available
2020-07-18 15:00:09 +02:00
Matteo Pagliazzi
4de5140cf7 fix(tests): remove exclusive unit test 2020-07-17 19:14:02 +02:00
Matteo Pagliazzi
7de5a51247 fix(cors): expose rate limit headers to clients 2020-07-17 19:00:16 +02:00
Matteo Pagliazzi
f1173cee6a API v3 Rate Limiter (#12117)
* simplify ip address management by using the trust proxy express option

* add setupExpress file

* fix redirects middleware tests

* fix lint

* short circuit the ip blocking middleware

* basic implementation with ip based limiting

* improve logging

* upgrade apidoc

* apidoc: add introduction section

* fix lint

* fix tests

* fix lint

* add unit tests for rate limiter

* do not send retry-after header when points are available

* automatically fix lint

* fix more lint issues

* use userId as key for rate limit when available
2020-07-17 16:13:51 +02:00
negue
0261d12bd9 Profile Page: redesign gear label (#12395)
* redesign gear label + lintOnSave not in development environment

* fix lint / build

* fix(lint): remove extra parens

* fix break-word + remove lintOnSave

* remove unneeded styles

Co-authored-by: Sabe Jones <sabrecat@gmail.com>
2020-07-17 12:43:27 +02:00
Sabe Jones
e3bcc48481 chore(npm): update package lock 2020-07-16 20:37:40 +00:00
Melior
8bbb0ddaee Merge branch 'origin/develop' into Weblate. 2020-07-16 22:16:18 +02:00
Melior
7a0733f5ac Translated using Weblate (German)
Currently translated at 100.0% (146 of 146 strings)

Translation: Habitica/Pets
Translate-URL: https://translate.habitica.com/projects/habitica/pets/de/

Translated using Weblate (German)

Currently translated at 100.0% (702 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/de/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (140 of 140 strings)

Translation: Habitica/Quests
Translate-URL: https://translate.habitica.com/projects/habitica/quests/zh_Hans/

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (2119 of 2119 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/pt_BR/

Translated using Weblate (Italian)

Currently translated at 100.0% (213 of 213 strings)

Translation: Habitica/Tasks
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/it/

Translated using Weblate (Czech)

Currently translated at 95.3% (203 of 213 strings)

Translation: Habitica/Tasks
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/cs/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (140 of 140 strings)

Translation: Habitica/Quests
Translate-URL: https://translate.habitica.com/projects/habitica/quests/zh_Hans/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (140 of 140 strings)

Translation: Habitica/Quests
Translate-URL: https://translate.habitica.com/projects/habitica/quests/zh_Hans/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (28 of 28 strings)

Translation: Habitica/Rebirth
Translate-URL: https://translate.habitica.com/projects/habitica/rebirth/zh_Hans/

Translated using Weblate (Italian)

Currently translated at 100.0% (173 of 173 strings)

Translation: Habitica/Npc
Translate-URL: https://translate.habitica.com/projects/habitica/npc/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (8 of 8 strings)

Translation: Habitica/Overview
Translate-URL: https://translate.habitica.com/projects/habitica/overview/it/

Translated using Weblate (Czech)

Currently translated at 83.6% (1773 of 2119 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/cs/

Translated using Weblate (Italian)

Currently translated at 100.0% (328 of 328 strings)

Translation: Habitica/Front
Translate-URL: https://translate.habitica.com/projects/habitica/front/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (63 of 63 strings)

Translation: Habitica/Defaulttasks
Translate-URL: https://translate.habitica.com/projects/habitica/defaulttasks/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (702 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/it/

Translated using Weblate (Czech)

Currently translated at 100.0% (70 of 70 strings)

Translation: Habitica/Contrib
Translate-URL: https://translate.habitica.com/projects/habitica/contrib/cs/

Translated using Weblate (Czech)

Currently translated at 100.0% (134 of 134 strings)

Translation: Habitica/Challenge
Translate-URL: https://translate.habitica.com/projects/habitica/challenge/cs/

Translated using Weblate (Czech)

Currently translated at 88.2% (479 of 543 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/cs/

Translated using Weblate (Czech)

Currently translated at 100.0% (211 of 211 strings)

Translation: Habitica/Settings
Translate-URL: https://translate.habitica.com/projects/habitica/settings/cs/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (140 of 140 strings)

Translation: Habitica/Quests
Translate-URL: https://translate.habitica.com/projects/habitica/quests/zh_Hans/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (140 of 140 strings)

Translation: Habitica/Quests
Translate-URL: https://translate.habitica.com/projects/habitica/quests/zh_Hans/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (140 of 140 strings)

Translation: Habitica/Quests
Translate-URL: https://translate.habitica.com/projects/habitica/quests/zh_Hans/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (140 of 140 strings)

Translation: Habitica/Quests
Translate-URL: https://translate.habitica.com/projects/habitica/quests/zh_Hans/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (140 of 140 strings)

Translation: Habitica/Quests
Translate-URL: https://translate.habitica.com/projects/habitica/quests/zh_Hans/

Translated using Weblate (Czech)

Currently translated at 100.0% (83 of 83 strings)

Translation: Habitica/Achievements
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/cs/

Translated using Weblate (Czech)

Currently translated at 100.0% (83 of 83 strings)

Translation: Habitica/Achievements
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/cs/

Translated using Weblate (Czech)

Currently translated at 100.0% (83 of 83 strings)

Translation: Habitica/Achievements
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/cs/

Translated using Weblate (Czech)

Currently translated at 100.0% (83 of 83 strings)

Translation: Habitica/Achievements
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/cs/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (28 of 28 strings)

Translation: Habitica/Rebirth
Translate-URL: https://translate.habitica.com/projects/habitica/rebirth/zh_Hans/

Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (56 of 56 strings)

Translation: Habitica/Faq
Translate-URL: https://translate.habitica.com/projects/habitica/faq/zh_Hant/

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (8 of 8 strings)

Translation: Habitica/Overview
Translate-URL: https://translate.habitica.com/projects/habitica/overview/vi/

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (146 of 146 strings)

Translation: Habitica/Pets
Translate-URL: https://translate.habitica.com/projects/habitica/pets/pt_BR/

Translated using Weblate (French)

Currently translated at 100.0% (146 of 146 strings)

Translation: Habitica/Pets
Translate-URL: https://translate.habitica.com/projects/habitica/pets/fr/

Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (146 of 146 strings)

Translation: Habitica/Pets
Translate-URL: https://translate.habitica.com/projects/habitica/pets/zh_Hant/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (146 of 146 strings)

Translation: Habitica/Pets
Translate-URL: https://translate.habitica.com/projects/habitica/pets/zh_Hans/

Translated using Weblate (Italian)

Currently translated at 100.0% (146 of 146 strings)

Translation: Habitica/Pets
Translate-URL: https://translate.habitica.com/projects/habitica/pets/it/

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (146 of 146 strings)

Translation: Habitica/Pets
Translate-URL: https://translate.habitica.com/projects/habitica/pets/en_GB/

Translated using Weblate (Italian)

Currently translated at 100.0% (493 of 493 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (2119 of 2119 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (328 of 328 strings)

Translation: Habitica/Front
Translate-URL: https://translate.habitica.com/projects/habitica/front/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (702 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (124 of 124 strings)

Translation: Habitica/Communityguidelines
Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (230 of 230 strings)

Translation: Habitica/Character
Translate-URL: https://translate.habitica.com/projects/habitica/character/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (134 of 134 strings)

Translation: Habitica/Challenge
Translate-URL: https://translate.habitica.com/projects/habitica/challenge/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (543 of 543 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/it/

Translated using Weblate (Japanese)

Currently translated at 100.0% (146 of 146 strings)

Translation: Habitica/Pets
Translate-URL: https://translate.habitica.com/projects/habitica/pets/ja/

Translated using Weblate (German)

Currently translated at 99.3% (145 of 146 strings)

Translation: Habitica/Pets
Translate-URL: https://translate.habitica.com/projects/habitica/pets/de/

Translated using Weblate (German)

Currently translated at 99.2% (2103 of 2119 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/de/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (702 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/zh_Hans/
2020-07-16 22:16:08 +02:00
Sabe Jones
474bc6a2b6 4.148.3 2020-07-16 15:12:06 -05:00
Sabe Jones
0af5593611 chore(sprites): compile 2020-07-16 15:11:49 -05:00
Sabe Jones
1b190a594a chore(news): Bailey 2020-07-16 15:11:40 -05:00
Sabe Jones
e54bd8f242 Merge branch 'develop' into release 2020-07-16 14:58:25 -05:00
Melior
0cc7c4a078 Merge branch 'origin/develop' into Weblate. 2020-07-14 23:22:58 +02:00
Melior
254a5cf423 Translated using Weblate (Vietnamese)
Currently translated at 100.0% (134 of 134 strings)

Translation: Habitica/Challenge
Translate-URL: https://translate.habitica.com/projects/habitica/challenge/vi/

Translated using Weblate (English (Pirate))

Currently translated at 97.9% (483 of 493 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/en@pirate/

Translated using Weblate (Vietnamese)

Currently translated at 99.2% (133 of 134 strings)

Translation: Habitica/Challenge
Translate-URL: https://translate.habitica.com/projects/habitica/challenge/vi/

Translated using Weblate (Thai)

Currently translated at 98.5% (132 of 134 strings)

Translation: Habitica/Challenge
Translate-URL: https://translate.habitica.com/projects/habitica/challenge/th/

Translated using Weblate (Slovenian)

Currently translated at 99.2% (133 of 134 strings)

Translation: Habitica/Challenge
Translate-URL: https://translate.habitica.com/projects/habitica/challenge/sl/

Translated using Weblate (Lithuanian)

Currently translated at 100.0% (134 of 134 strings)

Translation: Habitica/Challenge
Translate-URL: https://translate.habitica.com/projects/habitica/challenge/lt/

Translated using Weblate (Bosnian)

Currently translated at 100.0% (134 of 134 strings)

Translation: Habitica/Challenge
Translate-URL: https://translate.habitica.com/projects/habitica/challenge/bs/

Translated using Weblate (Arabic)

Currently translated at 99.2% (133 of 134 strings)

Translation: Habitica/Challenge
Translate-URL: https://translate.habitica.com/projects/habitica/challenge/ar/

Translated using Weblate (Czech)

Currently translated at 87.8% (123 of 140 strings)

Translation: Habitica/Quests
Translate-URL: https://translate.habitica.com/projects/habitica/quests/cs/

Translated using Weblate (Japanese)

Currently translated at 88.2% (1871 of 2119 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ja/

Translated using Weblate (Czech)

Currently translated at 99.3% (296 of 298 strings)

Translation: Habitica/Generic
Translate-URL: https://translate.habitica.com/projects/habitica/generic/cs/

Translated using Weblate (Czech)

Currently translated at 99.2% (133 of 134 strings)

Translation: Habitica/Challenge
Translate-URL: https://translate.habitica.com/projects/habitica/challenge/cs/

Translated using Weblate (Japanese)

Currently translated at 97.1% (682 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/ja/

Translated using Weblate (Italian)

Currently translated at 100.0% (250 of 250 strings)

Translation: Habitica/Subscriber
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (2119 of 2119 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/it/

Translated using Weblate (Japanese)

Currently translated at 88.2% (1869 of 2119 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ja/

Translated using Weblate (Italian)

Currently translated at 100.0% (83 of 83 strings)

Translation: Habitica/Achievements
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (702 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (360 of 360 strings)

Translation: Habitica/Content
Translate-URL: https://translate.habitica.com/projects/habitica/content/it/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (83 of 83 strings)

Translation: Habitica/Achievements
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/zh_Hans/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (702 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/zh_Hans/

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (543 of 543 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/pt_BR/

Translated using Weblate (Japanese)

Currently translated at 87.9% (1863 of 2119 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ja/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (702 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/zh_Hans/

Translated using Weblate (Japanese)

Currently translated at 87.8% (1861 of 2119 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ja/

Translated using Weblate (Italian)

Currently translated at 100.0% (83 of 83 strings)

Translation: Habitica/Achievements
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/it/

Translated using Weblate (Bengali)

Currently translated at 2.7% (4 of 143 strings)

Translation: Habitica/Pets
Translate-URL: https://translate.habitica.com/projects/habitica/pets/bn/

Translated using Weblate (Bengali)

Currently translated at 11.2% (37 of 328 strings)

Translation: Habitica/Front
Translate-URL: https://translate.habitica.com/projects/habitica/front/bn/

Translated using Weblate (Bengali)

Currently translated at 100.0% (134 of 134 strings)

Translation: Habitica/Challenge
Translate-URL: https://translate.habitica.com/projects/habitica/challenge/bn/

Translated using Weblate (Bengali)

Currently translated at 90.1% (192 of 213 strings)

Translation: Habitica/Tasks
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/bn/

Translated using Weblate (Bengali)

Currently translated at 8.4% (7 of 83 strings)

Translation: Habitica/Achievements
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/bn/

Translated using Weblate (Russian)

Currently translated at 100.0% (213 of 213 strings)

Translation: Habitica/Tasks
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/ru/

Translated using Weblate (Russian)

Currently translated at 96.5% (167 of 173 strings)

Translation: Habitica/Npc
Translate-URL: https://translate.habitica.com/projects/habitica/npc/ru/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (83 of 83 strings)

Translation: Habitica/Achievements
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/zh_Hans/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (702 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/zh_Hans/

Translated using Weblate (Czech)

Currently translated at 98.5% (66 of 67 strings)

Translation: Habitica/Messages
Translate-URL: https://translate.habitica.com/projects/habitica/messages/cs/

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (2119 of 2119 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/pt_BR/

Translated using Weblate (Japanese)

Currently translated at 87.7% (1859 of 2119 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ja/

Translated using Weblate (Portuguese (Brazil))

Currently translated at 98.7% (2093 of 2119 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/pt_BR/

Translated using Weblate (Czech)

Currently translated at 98.5% (66 of 67 strings)

Translation: Habitica/Messages
Translate-URL: https://translate.habitica.com/projects/habitica/messages/cs/

Translated using Weblate (Czech)

Currently translated at 98.5% (66 of 67 strings)

Translation: Habitica/Messages
Translate-URL: https://translate.habitica.com/projects/habitica/messages/cs/

Translated using Weblate (Czech)

Currently translated at 100.0% (27 of 27 strings)

Translation: Habitica/Loginincentives
Translate-URL: https://translate.habitica.com/projects/habitica/loginincentives/cs/

Translated using Weblate (Italian)

Currently translated at 100.0% (493 of 493 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/it/

Translated using Weblate (Czech)

Currently translated at 100.0% (6 of 6 strings)

Translation: Habitica/Inventory
Translate-URL: https://translate.habitica.com/projects/habitica/inventory/cs/

Translated using Weblate (Portuguese (Brazil))

Currently translated at 98.6% (2090 of 2119 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/pt_BR/

Translated using Weblate (Czech)

Currently translated at 100.0% (230 of 230 strings)

Translation: Habitica/Character
Translate-URL: https://translate.habitica.com/projects/habitica/character/cs/

Translated using Weblate (Czech)

Currently translated at 99.5% (210 of 211 strings)

Translation: Habitica/Settings
Translate-URL: https://translate.habitica.com/projects/habitica/settings/cs/

Translated using Weblate (Czech)

Currently translated at 95.7% (202 of 211 strings)

Translation: Habitica/Settings
Translate-URL: https://translate.habitica.com/projects/habitica/settings/cs/

Translated using Weblate (Czech)

Currently translated at 95.7% (202 of 211 strings)

Translation: Habitica/Settings
Translate-URL: https://translate.habitica.com/projects/habitica/settings/cs/

Translated using Weblate (Czech)

Currently translated at 100.0% (230 of 230 strings)

Translation: Habitica/Character
Translate-URL: https://translate.habitica.com/projects/habitica/character/cs/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (328 of 328 strings)

Translation: Habitica/Front
Translate-URL: https://translate.habitica.com/projects/habitica/front/zh_Hans/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (702 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/zh_Hans/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (230 of 230 strings)

Translation: Habitica/Character
Translate-URL: https://translate.habitica.com/projects/habitica/character/zh_Hans/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (230 of 230 strings)

Translation: Habitica/Character
Translate-URL: https://translate.habitica.com/projects/habitica/character/zh_Hans/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (230 of 230 strings)

Translation: Habitica/Character
Translate-URL: https://translate.habitica.com/projects/habitica/character/zh_Hans/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (230 of 230 strings)

Translation: Habitica/Character
Translate-URL: https://translate.habitica.com/projects/habitica/character/zh_Hans/

Translated using Weblate (Greek)

Currently translated at 75.5% (410 of 543 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/el/

Translated using Weblate (Arabic)

Currently translated at 75.5% (410 of 543 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/ar/

Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (213 of 213 strings)

Translation: Habitica/Tasks
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/zh_Hant/

Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (250 of 250 strings)

Translation: Habitica/Subscriber
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/zh_Hant/

Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (57 of 57 strings)

Translation: Habitica/Spells
Translate-URL: https://translate.habitica.com/projects/habitica/spells/zh_Hant/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (493 of 493 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/zh_Hans/

Translated using Weblate (Italian)

Currently translated at 100.0% (493 of 493 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/it/

Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (2119 of 2119 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/zh_Hant/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (2119 of 2119 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/zh_Hans/

Translated using Weblate (Italian)

Currently translated at 100.0% (2119 of 2119 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/it/

Translated using Weblate (French)

Currently translated at 100.0% (2119 of 2119 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/fr/

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (2119 of 2119 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/en_GB/

Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (56 of 56 strings)

Translation: Habitica/Faq
Translate-URL: https://translate.habitica.com/projects/habitica/faq/zh_Hant/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (56 of 56 strings)

Translation: Habitica/Faq
Translate-URL: https://translate.habitica.com/projects/habitica/faq/zh_Hans/

Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (702 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/zh_Hant/

Translated using Weblate (Czech)

Currently translated at 99.4% (358 of 360 strings)

Translation: Habitica/Content
Translate-URL: https://translate.habitica.com/projects/habitica/content/cs/

Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (543 of 543 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/zh_Hant/

Translated using Weblate (Italian)

Currently translated at 100.0% (543 of 543 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/it/

Translated using Weblate (French)

Currently translated at 100.0% (543 of 543 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/fr/

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (543 of 543 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/en_GB/

Translated using Weblate (Italian)

Currently translated at 100.0% (83 of 83 strings)

Translation: Habitica/Achievements
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/it/

Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (56 of 56 strings)

Translation: Habitica/Faq
Translate-URL: https://translate.habitica.com/projects/habitica/faq/zh_Hant/

Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (56 of 56 strings)

Translation: Habitica/Faq
Translate-URL: https://translate.habitica.com/projects/habitica/faq/zh_Hant/

Translated using Weblate (Italian)

Currently translated at 100.0% (543 of 543 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (543 of 543 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (83 of 83 strings)

Translation: Habitica/Achievements
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (83 of 83 strings)

Translation: Habitica/Achievements
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/it/

Translated using Weblate (Japanese)

Currently translated at 87.5% (1856 of 2119 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ja/

Translated using Weblate (Japanese)

Currently translated at 87.4% (1853 of 2119 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ja/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (702 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/zh_Hans/

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (543 of 543 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/vi/

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (250 of 250 strings)

Translation: Habitica/Subscriber
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/vi/

Translated using Weblate (French)

Currently translated at 99.7% (2114 of 2119 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/fr/

Translated using Weblate (French)

Currently translated at 99.0% (538 of 543 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/fr/

Translated using Weblate (Japanese)

Currently translated at 87.3% (1852 of 2119 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ja/

Translated using Weblate (Italian)

Currently translated at 93.2% (1977 of 2119 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/it/

Translated using Weblate (Hindi)

Currently translated at 100.0% (6 of 6 strings)

Translation: Habitica/Inventory
Translate-URL: https://translate.habitica.com/projects/habitica/inventory/hi/

Translated using Weblate (German)

Currently translated at 100.0% (140 of 140 strings)

Translation: Habitica/Quests
Translate-URL: https://translate.habitica.com/projects/habitica/quests/de/

Translated using Weblate (German)

Currently translated at 100.0% (173 of 173 strings)

Translation: Habitica/Npc
Translate-URL: https://translate.habitica.com/projects/habitica/npc/de/

Translated using Weblate (Italian)

Currently translated at 93.0% (1972 of 2119 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/it/

Translated using Weblate (German)

Currently translated at 100.0% (328 of 328 strings)

Translation: Habitica/Front
Translate-URL: https://translate.habitica.com/projects/habitica/front/de/

Translated using Weblate (German)

Currently translated at 100.0% (63 of 63 strings)

Translation: Habitica/Defaulttasks
Translate-URL: https://translate.habitica.com/projects/habitica/defaulttasks/de/

Translated using Weblate (Japanese)

Currently translated at 100.0% (543 of 543 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/ja/

Translated using Weblate (Italian)

Currently translated at 100.0% (543 of 543 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (543 of 543 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/it/

Translated using Weblate (German)

Currently translated at 100.0% (543 of 543 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/de/

Translated using Weblate (Hindi)

Currently translated at 100.0% (83 of 83 strings)

Translation: Habitica/Achievements
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/hi/

Translated using Weblate (Hindi)

Currently translated at 100.0% (83 of 83 strings)

Translation: Habitica/Achievements
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/hi/

Translated using Weblate (German)

Currently translated at 100.0% (56 of 56 strings)

Translation: Habitica/Faq
Translate-URL: https://translate.habitica.com/projects/habitica/faq/de/

Translated using Weblate (German)

Currently translated at 100.0% (56 of 56 strings)

Translation: Habitica/Faq
Translate-URL: https://translate.habitica.com/projects/habitica/faq/de/

Translated using Weblate (German)

Currently translated at 100.0% (360 of 360 strings)

Translation: Habitica/Content
Translate-URL: https://translate.habitica.com/projects/habitica/content/de/

Translated using Weblate (Korean)

Currently translated at 51.0% (73 of 143 strings)

Translation: Habitica/Pets
Translate-URL: https://translate.habitica.com/projects/habitica/pets/ko/

Translated using Weblate (Korean)

Currently translated at 70.2% (130 of 185 strings)

Translation: Habitica/Limited
Translate-URL: https://translate.habitica.com/projects/habitica/limited/ko/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (213 of 213 strings)

Translation: Habitica/Tasks
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/zh_Hans/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (2119 of 2119 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/zh_Hans/

Translated using Weblate (German)

Currently translated at 99.0% (2099 of 2119 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/de/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (702 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/zh_Hans/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (543 of 543 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/zh_Hans/

Translated using Weblate (Russian)

Currently translated at 98.7% (536 of 543 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/ru/

Translated using Weblate (German)

Currently translated at 99.4% (540 of 543 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/de/
2020-07-14 23:22:49 +02:00
Sabe Jones
18bc8c3d63 4.148.2 2020-07-14 16:11:42 -05:00
Sabe Jones
15753de3a1 feat(content): Seafoam and Amigos 2020-07-14 16:11:34 -05:00
Matteo Pagliazzi
c93bf3e498 MongoDB Transactions (#12335)
* add run-rs to dependencies

* wip: add replica set to api unit github action

* wip: add replica set to api unit github action

* wip: fix gh actions mongodb replica set setting

* usa replica set for integration tests

* add correct mongodb version matrix for integration tests

* use different db connection on gh actions

* Revert "use different db connection on gh actions"

This reverts commit aa8db759d3.

* add example transaction

* add mongo script to package.json

* abstract mongodb utils, connect using hostname on windows

* npm scripts: mongo -> mongo:dev

* add setup script for run-rs on windows

* gh actions: run in test environment

* remove test files

* better error handling, use cross-spawn to avoid issues on windows

* fix lint
2020-07-14 18:55:47 +02:00
PitiTheGrey
e89ff95a21 Add Bulk Feed via query parameter (#12384)
* Update feed.js

New Tests for bulk feeding

* Update POST-user_feed_pet_food.test.js

Added test for bulk-feeding

* Update user.js

Added 'query paramter' for bulk feeding

* Update pets.json

Added "tooMuchFood" for bulk feeding pets

* Update feed.js

Added query parameter option for bulk feeding pets.

* Update feed.js

fixing lint
(bulk feeding)

* Update POST-user_feed_pet_food.test.js

adjustments for testing bulk feeding

* Update feed.js

Bulk feeding 
amount as integer

* Update pets.json

added invalidAmount for bulk feeding

* Update feed.js

Bulk feeding  
Error handling

* Update feed.js

Bulk - feed  
no hardcoded values

* Update pets.json

Get rid of my german accent.
2020-07-13 16:04:03 +02:00
Jalansh
a02c4c1cfd WIP. Accepting a redundant party invite will not remove the user from the party and let the user still be a part of it. Fixes #12291. (#12356)
* Getting the latest code

* Temporary fix for Redundant Party Invite. Needs changes.

* Added logic to check if the user is an existing member of the party that the user is invited to.

* Added a test case for redundant party invite check.

* Changed the test case for redundant party invite to see if it runs successfully.

* Made changes to the test cases.

* Fixed lint errors.

* Removed the exclusive mocha test.

* Referred the issue in the name of the new test case.

* Modified test case to check its veracity.

* Checking if the update statement is working or not.
2020-07-13 16:00:34 +02:00
Matteo Pagliazzi
616a0b7509 fix(tests): add informative message if mongo is not running 2020-07-13 16:00:04 +02:00
Matteo Pagliazzi
2a0bc030d8 Merge pull request #12385 from HabitRPG/fix/time-travelers-pinning
fix filter reset on pinning items
2020-07-13 11:53:03 +02:00
Matteo Pagliazzi
42b00ed381 build(deps): bump mongoose from 5.9.21 to 5.9.23 (#12391)
Bumps [mongoose](https://github.com/Automattic/mongoose) from 5.9.21 to 5.9.23.
- [Release notes](https://github.com/Automattic/mongoose/releases)
- [Changelog](https://github.com/Automattic/mongoose/blob/master/History.md)
- [Commits](https://github.com/Automattic/mongoose/compare/5.9.21...5.9.23)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-07-13 11:45:07 +02:00
dependabot-preview[bot]
928c88f2da build(deps): bump lodash from 4.17.15 to 4.17.19 (#12387)
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.15 to 4.17.19.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.15...4.17.19)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-07-13 11:44:56 +02:00
dependabot-preview[bot]
768e71228c build(deps): bump mongoose from 5.9.21 to 5.9.23
Bumps [mongoose](https://github.com/Automattic/mongoose) from 5.9.21 to 5.9.23.
- [Release notes](https://github.com/Automattic/mongoose/releases)
- [Changelog](https://github.com/Automattic/mongoose/blob/master/History.md)
- [Commits](https://github.com/Automattic/mongoose/compare/5.9.21...5.9.23)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-07-13 08:45:51 +00:00
dependabot-preview[bot]
6082f77977 build(deps): bump lodash from 4.17.15 to 4.17.19 in /website/client (#12386)
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.15 to 4.17.19.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.15...4.17.19)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-07-13 10:43:58 +02:00
dependabot-preview[bot]
f6717a0bc1 build(deps): bump vuedraggable from 2.23.2 to 2.24.0 in /website/client (#12388)
Bumps [vuedraggable](https://github.com/SortableJS/Vue.Draggable) from 2.23.2 to 2.24.0.
- [Release notes](https://github.com/SortableJS/Vue.Draggable/releases)
- [Commits](https://github.com/SortableJS/Vue.Draggable/compare/v2.23.2...v2.24.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-07-13 10:43:42 +02:00
dependabot-preview[bot]
9b3f8981e5 build(deps): bump node-gcm from 1.0.2 to 1.0.3 (#12389)
Bumps [node-gcm](https://github.com/ToothlessGear/node-gcm) from 1.0.2 to 1.0.3.
- [Release notes](https://github.com/ToothlessGear/node-gcm/releases)
- [Changelog](https://github.com/ToothlessGear/node-gcm/blob/master/CHANGELOG.md)
- [Commits](https://github.com/ToothlessGear/node-gcm/commits)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-07-13 10:43:27 +02:00
dependabot-preview[bot]
2758414b54 build(deps): bump sass from 1.26.9 to 1.26.10 in /website/client (#12392)
Bumps [sass](https://github.com/sass/dart-sass) from 1.26.9 to 1.26.10.
- [Release notes](https://github.com/sass/dart-sass/releases)
- [Changelog](https://github.com/sass/dart-sass/blob/master/CHANGELOG.md)
- [Commits](https://github.com/sass/dart-sass/compare/1.26.9...1.26.10)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-07-13 10:43:13 +02:00
dependabot-preview[bot]
e49aabdddf build(deps): bump got from 11.4.0 to 11.5.0 (#12393)
Bumps [got](https://github.com/sindresorhus/got) from 11.4.0 to 11.5.0.
- [Release notes](https://github.com/sindresorhus/got/releases)
- [Commits](https://github.com/sindresorhus/got/compare/v11.4.0...v11.5.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-07-13 10:42:58 +02:00
dependabot-preview[bot]
aa371de8ae build(deps): bump universal-analytics from 0.4.22 to 0.4.23 (#12394)
Bumps [universal-analytics](https://github.com/peaksandpies/universal-analytics) from 0.4.22 to 0.4.23.
- [Release notes](https://github.com/peaksandpies/universal-analytics/releases)
- [Changelog](https://github.com/peaksandpies/universal-analytics/blob/master/HISTORY.md)
- [Commits](https://github.com/peaksandpies/universal-analytics/commits)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-07-13 10:42:15 +02:00
negue
0acc7d19c5 fix filter reset on pinning items - fixes #9500 2020-07-12 20:43:51 +02:00
Matteo Pagliazzi
d861236f44 fix(cors): allow authorization header 2020-07-12 18:22:52 +02:00
Sabe Jones
195928e471 Merge branch 'release' into develop 2020-07-10 11:05:31 -05:00
Sabe Jones
c27d52098b 4.148.1 2020-07-10 11:04:56 -05:00
Sabe Jones
9e82ba261b fix(columns): avoid a Vue error on initial load 2020-07-10 10:50:27 -05:00
negue
566dd2b6b1 fix new pm canReceive (#12369) 2020-07-08 11:30:27 +02:00
Melior
1a769d4a45 Merge branch 'origin/develop' into Weblate. 2020-07-07 21:58:20 +02:00
Melior
f3fe1d76ad Translated using Weblate (Slovak)
Currently translated at 94.7% (54 of 57 strings)

Translation: Habitica/Spells
Translate-URL: https://translate.habitica.com/projects/habitica/spells/sk/

Translated using Weblate (Portuguese (Brazil))

Currently translated at 98.8% (2089 of 2113 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/pt_BR/

Translated using Weblate (Portuguese (Brazil))

Currently translated at 98.8% (2088 of 2113 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/pt_BR/

Translated using Weblate (Portuguese (Brazil))

Currently translated at 98.8% (2088 of 2113 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/pt_BR/

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (213 of 213 strings)

Translation: Habitica/Tasks
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/en_GB/

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (250 of 250 strings)

Translation: Habitica/Subscriber
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/en_GB/

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (57 of 57 strings)

Translation: Habitica/Spells
Translate-URL: https://translate.habitica.com/projects/habitica/spells/en_GB/

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (185 of 185 strings)

Translation: Habitica/Limited
Translate-URL: https://translate.habitica.com/projects/habitica/limited/en_GB/

Translated using Weblate (Slovak)

Currently translated at 100.0% (6 of 6 strings)

Translation: Habitica/Inventory
Translate-URL: https://translate.habitica.com/projects/habitica/inventory/sk/

Translated using Weblate (Portuguese)

Currently translated at 81.8% (1730 of 2113 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/pt/

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (2113 of 2113 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/en_GB/

Translated using Weblate (Slovak)

Currently translated at 100.0% (15 of 15 strings)

Translation: Habitica/Death
Translate-URL: https://translate.habitica.com/projects/habitica/death/sk/

Translated using Weblate (French)

Currently translated at 100.0% (702 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/fr/

Translated using Weblate (Slovak)

Currently translated at 100.0% (70 of 70 strings)

Translation: Habitica/Contrib
Translate-URL: https://translate.habitica.com/projects/habitica/contrib/sk/

Translated using Weblate (Czech)

Currently translated at 100.0% (70 of 70 strings)

Translation: Habitica/Contrib
Translate-URL: https://translate.habitica.com/projects/habitica/contrib/cs/

Translated using Weblate (Slovak)

Currently translated at 100.0% (134 of 134 strings)

Translation: Habitica/Challenge
Translate-URL: https://translate.habitica.com/projects/habitica/challenge/sk/

Translated using Weblate (Slovak)

Currently translated at 27.7% (23 of 83 strings)

Translation: Habitica/Achievements
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/sk/

Translated using Weblate (Korean)

Currently translated at 16.1% (53 of 328 strings)

Translation: Habitica/Front
Translate-URL: https://translate.habitica.com/projects/habitica/front/ko/

Translated using Weblate (Korean)

Currently translated at 93.8% (503 of 536 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/ko/

Translated using Weblate (Spanish)

Currently translated at 89.2% (223 of 250 strings)

Translation: Habitica/Subscriber
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/es/

Translated using Weblate (Japanese)

Currently translated at 87.6% (1851 of 2113 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ja/

Translated using Weblate (Italian)

Currently translated at 91.4% (1932 of 2113 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (360 of 360 strings)

Translation: Habitica/Content
Translate-URL: https://translate.habitica.com/projects/habitica/content/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (360 of 360 strings)

Translation: Habitica/Content
Translate-URL: https://translate.habitica.com/projects/habitica/content/it/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (185 of 185 strings)

Translation: Habitica/Limited
Translate-URL: https://translate.habitica.com/projects/habitica/limited/zh_Hans/

Translated using Weblate (Chinese (Simplified))

Currently translated at 98.8% (2089 of 2113 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/zh_Hans/

Translated using Weblate (Chinese (Simplified))

Currently translated at 99.8% (701 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/zh_Hans/

Translated using Weblate (Vietnamese)

Currently translated at 85.6% (214 of 250 strings)

Translation: Habitica/Subscriber
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/vi/

Translated using Weblate (French)

Currently translated at 100.0% (702 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/fr/

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (213 of 213 strings)

Translation: Habitica/Tasks
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/vi/

Translated using Weblate (Vietnamese)

Currently translated at 83.6% (209 of 250 strings)

Translation: Habitica/Subscriber
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/vi/

Translated using Weblate (Hindi)

Currently translated at 12.0% (10 of 83 strings)

Translation: Habitica/Achievements
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/hi/

Translated using Weblate (Hindi)

Currently translated at 12.0% (10 of 83 strings)

Translation: Habitica/Achievements
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/hi/

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (185 of 185 strings)

Translation: Habitica/Limited
Translate-URL: https://translate.habitica.com/projects/habitica/limited/vi/

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (211 of 211 strings)

Translation: Habitica/Settings
Translate-URL: https://translate.habitica.com/projects/habitica/settings/vi/

Translated using Weblate (Japanese)

Currently translated at 100.0% (8 of 8 strings)

Translation: Habitica/Overview
Translate-URL: https://translate.habitica.com/projects/habitica/overview/ja/

Translated using Weblate (Japanese)

Currently translated at 100.0% (493 of 493 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/ja/

Translated using Weblate (Italian)

Currently translated at 90.6% (1915 of 2113 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/it/

Translated using Weblate (Tagalog)

Currently translated at 89.2% (190 of 213 strings)

Translation: Habitica/Tasks
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/tl/

Translated using Weblate (Javanese)

Currently translated at 89.2% (190 of 213 strings)

Translation: Habitica/Tasks
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/jv/

Translated using Weblate (Arabic)

Currently translated at 89.2% (190 of 213 strings)

Translation: Habitica/Tasks
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/ar/

Translated using Weblate (Croatian)

Currently translated at 88.7% (623 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/hr/

Translated using Weblate (Persian)

Currently translated at 100.0% (27 of 27 strings)

Translation: Habitica/Loginincentives
Translate-URL: https://translate.habitica.com/projects/habitica/loginincentives/fa/

Translated using Weblate (Tagalog)

Currently translated at 80.5% (1701 of 2113 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/tl/

Translated using Weblate (Korean)

Currently translated at 80.5% (1701 of 2113 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ko/

Translated using Weblate (Galician)

Currently translated at 80.5% (1701 of 2113 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/gl/

Translated using Weblate (Croatian)

Currently translated at 98.3% (122 of 124 strings)

Translation: Habitica/Communityguidelines
Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/hr/

Translated using Weblate (Finnish)

Currently translated at 98.3% (122 of 124 strings)

Translation: Habitica/Communityguidelines
Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/fi/

Translated using Weblate (Greek)

Currently translated at 98.3% (122 of 124 strings)

Translation: Habitica/Communityguidelines
Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/el/

Translated using Weblate (Vietnamese)

Currently translated at 98.5% (208 of 211 strings)

Translation: Habitica/Settings
Translate-URL: https://translate.habitica.com/projects/habitica/settings/vi/

Translated using Weblate (Dutch)

Currently translated at 97.8% (2067 of 2113 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/nl/

Translated using Weblate (Japanese)

Currently translated at 87.4% (1847 of 2113 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ja/

Translated using Weblate (Spanish)

Currently translated at 100.0% (124 of 124 strings)

Translation: Habitica/Communityguidelines
Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/es/

Translated using Weblate (Italian)

Currently translated at 89.3% (1889 of 2113 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/it/

Translated using Weblate (Chinese (Simplified))

Currently translated at 99.8% (701 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/zh_Hans/

Translated using Weblate (Italian)

Currently translated at 100.0% (211 of 211 strings)

Translation: Habitica/Settings
Translate-URL: https://translate.habitica.com/projects/habitica/settings/it/

Translated using Weblate (Italian)

Currently translated at 88.4% (1868 of 2113 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/it/

Translated using Weblate (Japanese)

Currently translated at 87.1% (1841 of 2113 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ja/

Translated using Weblate (French)

Currently translated at 100.0% (8 of 8 strings)

Translation: Habitica/Overview
Translate-URL: https://translate.habitica.com/projects/habitica/overview/fr/

Translated using Weblate (Italian)

Currently translated at 100.0% (493 of 493 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/it/

Translated using Weblate (Italian)

Currently translated at 88.3% (1867 of 2113 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/it/

Translated using Weblate (Italian)

Currently translated at 88.3% (1867 of 2113 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (328 of 328 strings)

Translation: Habitica/Front
Translate-URL: https://translate.habitica.com/projects/habitica/front/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (702 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (124 of 124 strings)

Translation: Habitica/Communityguidelines
Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (134 of 134 strings)

Translation: Habitica/Challenge
Translate-URL: https://translate.habitica.com/projects/habitica/challenge/it/

Translated using Weblate (Chinese (Simplified))

Currently translated at 99.8% (701 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/zh_Hans/

Translated using Weblate (Italian)

Currently translated at 86.8% (1835 of 2113 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/it/

Translated using Weblate (Korean)

Currently translated at 89.2% (25 of 28 strings)

Translation: Habitica/Rebirth
Translate-URL: https://translate.habitica.com/projects/habitica/rebirth/ko/

Translated using Weblate (Italian)

Currently translated at 86.0% (1819 of 2113 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/it/

Translated using Weblate (Japanese)

Currently translated at 87.1% (1841 of 2113 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ja/

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (493 of 493 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/vi/

Translated using Weblate (Portuguese (Brazil))

Currently translated at 98.7% (2086 of 2113 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/pt_BR/

Translated using Weblate (Italian)

Currently translated at 85.4% (1805 of 2113 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/it/

Translated using Weblate (Chinese (Simplified))

Currently translated at 99.8% (701 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/zh_Hans/

Translated using Weblate (Vietnamese)

Currently translated at 98.5% (486 of 493 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/vi/

Translated using Weblate (Italian)

Currently translated at 82.6% (1747 of 2113 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/it/

Translated using Weblate (French)

Currently translated at 100.0% (702 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/fr/

Translated using Weblate (Japanese)

Currently translated at 86.9% (1837 of 2113 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ja/

Translated using Weblate (French)

Currently translated at 100.0% (702 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/fr/

Translated using Weblate (French)

Currently translated at 100.0% (2113 of 2113 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/fr/

Translated using Weblate (Italian)

Currently translated at 100.0% (213 of 213 strings)

Translation: Habitica/Tasks
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/it/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (250 of 250 strings)

Translation: Habitica/Subscriber
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/zh_Hans/

Translated using Weblate (Italian)

Currently translated at 100.0% (140 of 140 strings)

Translation: Habitica/Quests
Translate-URL: https://translate.habitica.com/projects/habitica/quests/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (143 of 143 strings)

Translation: Habitica/Pets
Translate-URL: https://translate.habitica.com/projects/habitica/pets/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (173 of 173 strings)

Translation: Habitica/Npc
Translate-URL: https://translate.habitica.com/projects/habitica/npc/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (67 of 67 strings)

Translation: Habitica/Messages
Translate-URL: https://translate.habitica.com/projects/habitica/messages/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (6 of 6 strings)

Translation: Habitica/Inventory
Translate-URL: https://translate.habitica.com/projects/habitica/inventory/it/

Translated using Weblate (Italian)

Currently translated at 82.6% (1747 of 2113 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (56 of 56 strings)

Translation: Habitica/Faq
Translate-URL: https://translate.habitica.com/projects/habitica/faq/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (702 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (124 of 124 strings)

Translation: Habitica/Communityguidelines
Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (83 of 83 strings)

Translation: Habitica/Achievements
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/it/

Translated using Weblate (Italian)

Currently translated at 82.6% (1747 of 2113 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/it/

Translated using Weblate (Chinese (Simplified))

Currently translated at 99.8% (701 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/zh_Hans/

Translated using Weblate (Italian)

Currently translated at 100.0% (702 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/it/

Translated using Weblate (Russian)

Currently translated at 99.5% (210 of 211 strings)

Translation: Habitica/Settings
Translate-URL: https://translate.habitica.com/projects/habitica/settings/ru/

Translated using Weblate (Italian)

Currently translated at 100.0% (702 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (702 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (702 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (702 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/it/

Translated using Weblate (Italian)

Currently translated at 99.4% (698 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/it/

Translated using Weblate (Italian)

Currently translated at 99.2% (697 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/it/
2020-07-07 21:58:10 +02:00
Sabe Jones
955c41f744 Merge branch 'release' into develop 2020-07-07 14:53:52 -05:00
Sabe Jones
c46b4e55c3 4.148.0 2020-07-07 14:49:31 -05:00
Sabe Jones
dc7b016950 chore(news): Bailey 2020-07-07 14:49:20 -05:00
Sabe Jones
264d6b466a chore(sprites): compile 2020-07-07 14:39:27 -05:00
Sabe Jones
c876d970eb fix(content): missing strings 2020-07-07 13:50:39 -05:00
dependabot-preview[bot]
00c9fabf8b build(deps): bump amplitude-js from 6.2.0 to 7.1.0 in /website/client (#12375)
Bumps [amplitude-js](https://github.com/amplitude/amplitude-javascript) from 6.2.0 to 7.1.0.
- [Release notes](https://github.com/amplitude/amplitude-javascript/releases)
- [Changelog](https://github.com/amplitude/Amplitude-JavaScript/blob/master/CHANGELOG.md)
- [Commits](https://github.com/amplitude/amplitude-javascript/commits)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-07-07 20:04:07 +02:00
dependabot-preview[bot]
82d1df67c9 build(deps): bump universal-analytics from 0.4.20 to 0.4.22 (#12373)
Bumps [universal-analytics](https://github.com/peaksandpies/universal-analytics) from 0.4.20 to 0.4.22.
- [Release notes](https://github.com/peaksandpies/universal-analytics/releases)
- [Changelog](https://github.com/peaksandpies/universal-analytics/blob/master/HISTORY.md)
- [Commits](https://github.com/peaksandpies/universal-analytics/commits)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-07-07 20:02:16 +02:00
dependabot-preview[bot]
9449e6a883 build(deps): bump got from 11.3.0 to 11.4.0 (#12372)
Bumps [got](https://github.com/sindresorhus/got) from 11.3.0 to 11.4.0.
- [Release notes](https://github.com/sindresorhus/got/releases)
- [Commits](https://github.com/sindresorhus/got/compare/v11.3.0...v11.4.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-07-07 20:01:17 +02:00
dependabot-preview[bot]
fa72684c53 build(deps): bump mongoose from 5.9.20 to 5.9.21 (#12371)
Bumps [mongoose](https://github.com/Automattic/mongoose) from 5.9.20 to 5.9.21.
- [Release notes](https://github.com/Automattic/mongoose/releases)
- [Changelog](https://github.com/Automattic/mongoose/blob/master/History.md)
- [Commits](https://github.com/Automattic/mongoose/compare/5.9.20...5.9.21)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-07-07 20:00:59 +02:00
Matteo Pagliazzi
45a22470fa fix 9351: sync player that casts spells immediately (#12367) 2020-07-05 00:01:02 +02:00
tsukimi2
a388abc124 Added code to update user tag list along with the existing code that already involves updating the user documents of challenge members (#12312)
* Piggybacking the updating of user tag list.

When a new task is being added to a challenge, added code to update user tag list along with the existing code that already involves syncing / updating the user documents of challenge members.

* Update comment on number of simulatenaeous users to be updated concurrently in TaskQueue.

* Added comment to explain previous commit caaca469f8 (Update comment on number of simulateneous users to be updated concurrently in TaskQueue)

* Added unit tests for testing commit caaca469f8 (Update comment on number of simulateneous users to be updated concurrently in TaskQueue)

* Removed unused lines from newly added test cases

* Implemented lint suggestions

* Update code with changes requested in PR
2020-07-03 21:55:59 +02:00
Sabe Jones
94dd64677f feat(content): armoire/bgs 2020-07-03 11:17:50 -05:00
JalanshMunshi
453d60b5bf WIP. Changed the way how memberCount is incremented or decremented. Fixes #12275 (#12308)
* Changed the way how memberCount is incremented or decremented.

* Updated the logic for incrementing/decrementing memberCount for parties and guilds.

* Fixed lint errors

* Added relevant comment. Changed the way how memberCount is updated to replace the usage of custom query.

* Fixed lint errors.

* Reverted changes owing to failing tests.

* Added relevant comments. Removed duplicate and unwanted code. Added await for async function call.

* Minor change due to lint error.

* Reverted changes for removing the party member when the menber leaves.
2020-07-03 16:51:45 +02:00
Robert Whitaker
af1d13d3a2 Fix bug where updated webhook options failed to save (fixes #12336) (#12342)
* Fix bug where updated webhook options failed to save

This bug was caused by Mongoose not creating getters/setters for array
elements (https://mongoosejs.com/docs/faq.html#array-changes-not-saved).
So, although the webhook was being updated properly, Mongoose was not
actually committing it to the database. Telling Mongoose that the array
of webhooks has changed via `markModified` fixes the issue.

Additionally, the relevant API test case was only checking whether or
not the webhook returned from the PUT endpoint matched the expected
update. Since the endpoint was returning the updated webhook without
querying the database again, this test case would pass. It has been
updated to check both the returned webhook as well as the version of the
webhook that is saved to the database against the expected. In other
words:

`assert returned === saved === expected`

Fixes #12336

* Call markModified on webhook.options instead of user.webhooks

This tells Mongoose that only the modified webhook's options changed
instead of telling it that the entire user.webhooks array changed,
saving a costly DB update.
2020-07-03 16:48:45 +02:00
Matteo Pagliazzi
680e86d2c9 Merge branch 'release' into develop 2020-07-03 15:07:29 +02:00
Matteo Pagliazzi
46c92d6171 4.147.3 2020-07-03 14:36:01 +02:00
Matteo Pagliazzi
1efcaa4a1d fix #12364, profile of logged in user did not show up correctly 2020-07-03 14:35:29 +02:00
Matteo Pagliazzi
cc5e3ed123 fix #12364, profile of logged in user did not show up correctly 2020-07-03 14:17:39 +02:00
Sabe Jones
c4d131744b fix(lint): let Curly breathe 2020-07-02 16:50:41 -05:00
Sabe Jones
a378106851 fix(script): better dupe matching 2020-07-02 20:56:19 +00:00
Melior
4a6c0168a9 Merge branch 'origin/develop' into Weblate. 2020-07-02 21:05:55 +02:00
Melior
9d29f065e9 Translated using Weblate (Portuguese (Brazil))
Currently translated at 98.7% (2086 of 2113 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/pt_BR/

Translated using Weblate (Italian)

Currently translated at 96.5% (678 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/it/

Translated using Weblate (Korean)

Currently translated at 15.8% (52 of 328 strings)

Translation: Habitica/Front
Translate-URL: https://translate.habitica.com/projects/habitica/front/ko/

Translated using Weblate (Latin)

Currently translated at 98.2% (56 of 57 strings)

Translation: Habitica/Spells
Translate-URL: https://translate.habitica.com/projects/habitica/spells/la/

Translated using Weblate (Korean)

Currently translated at 76.7% (43 of 56 strings)

Translation: Habitica/Faq
Translate-URL: https://translate.habitica.com/projects/habitica/faq/ko/

Translated using Weblate (Arabic)

Currently translated at 100.0% (12 of 12 strings)

Translation: Habitica/Merch
Translate-URL: https://translate.habitica.com/projects/habitica/merch/ar/

Translated using Weblate (Thai)

Currently translated at 97.3% (224 of 230 strings)

Translation: Habitica/Character
Translate-URL: https://translate.habitica.com/projects/habitica/character/th/

Translated using Weblate (Lithuanian)

Currently translated at 97.3% (224 of 230 strings)

Translation: Habitica/Character
Translate-URL: https://translate.habitica.com/projects/habitica/character/lt/

Translated using Weblate (Arabic)

Currently translated at 97.3% (224 of 230 strings)

Translation: Habitica/Character
Translate-URL: https://translate.habitica.com/projects/habitica/character/ar/

Translated using Weblate (Tamil)

Currently translated at 100.0% (134 of 134 strings)

Translation: Habitica/Challenge
Translate-URL: https://translate.habitica.com/projects/habitica/challenge/ta/

Translated using Weblate (Tamil)

Currently translated at 76.4% (410 of 536 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/ta/

Translated using Weblate (Malay)

Currently translated at 76.8% (412 of 536 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/ms/

Translated using Weblate (Hindi)

Currently translated at 76.4% (410 of 536 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/hi/

Translated using Weblate (Irish)

Currently translated at 76.4% (410 of 536 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/ga/

Translated using Weblate (Arabic)

Currently translated at 76.4% (410 of 536 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/ar/

Translated using Weblate (Arabic)

Currently translated at 89.2% (190 of 213 strings)

Translation: Habitica/Tasks
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/ar/

Translated using Weblate (Persian)

Currently translated at 78.8% (197 of 250 strings)

Translation: Habitica/Subscriber
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/fa/

Translated using Weblate (Arabic)

Currently translated at 88.7% (623 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/ar/

Translated using Weblate (Chinese (Hong Kong))

Currently translated at 8.4% (7 of 83 strings)

Translation: Habitica/Achievements
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/zh_Hant_HK/

Translated using Weblate (Chinese (Hong Kong))

Currently translated at 94.0% (63 of 67 strings)

Translation: Habitica/Messages
Translate-URL: https://translate.habitica.com/projects/habitica/messages/zh_Hant_HK/

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (67 of 67 strings)

Translation: Habitica/Messages
Translate-URL: https://translate.habitica.com/projects/habitica/messages/vi/

Translated using Weblate (Urdu (Pakistan))

Currently translated at 94.0% (63 of 67 strings)

Translation: Habitica/Messages
Translate-URL: https://translate.habitica.com/projects/habitica/messages/ur_PK/

Translated using Weblate (Klingon)

Currently translated at 94.0% (63 of 67 strings)

Translation: Habitica/Messages
Translate-URL: https://translate.habitica.com/projects/habitica/messages/tlh/

Translated using Weblate (Tagalog)

Currently translated at 94.0% (63 of 67 strings)

Translation: Habitica/Messages
Translate-URL: https://translate.habitica.com/projects/habitica/messages/tl/

Translated using Weblate (Thai)

Currently translated at 94.0% (63 of 67 strings)

Translation: Habitica/Messages
Translate-URL: https://translate.habitica.com/projects/habitica/messages/th/

Translated using Weblate (Tamil)

Currently translated at 94.0% (63 of 67 strings)

Translation: Habitica/Messages
Translate-URL: https://translate.habitica.com/projects/habitica/messages/ta/

Translated using Weblate (Swahili)

Currently translated at 94.0% (63 of 67 strings)

Translation: Habitica/Messages
Translate-URL: https://translate.habitica.com/projects/habitica/messages/sw/

Translated using Weblate (Sundanese)

Currently translated at 94.0% (63 of 67 strings)

Translation: Habitica/Messages
Translate-URL: https://translate.habitica.com/projects/habitica/messages/su/

Translated using Weblate (Slovenian)

Currently translated at 94.0% (63 of 67 strings)

Translation: Habitica/Messages
Translate-URL: https://translate.habitica.com/projects/habitica/messages/sl/

Translated using Weblate (Sinhala)

Currently translated at 94.0% (63 of 67 strings)

Translation: Habitica/Messages
Translate-URL: https://translate.habitica.com/projects/habitica/messages/si/

Translated using Weblate (Scots)

Currently translated at 94.0% (63 of 67 strings)

Translation: Habitica/Messages
Translate-URL: https://translate.habitica.com/projects/habitica/messages/sco/

Translated using Weblate (Norwegian Bokmål)

Currently translated at 94.0% (63 of 67 strings)

Translation: Habitica/Messages
Translate-URL: https://translate.habitica.com/projects/habitica/messages/nb_NO/

Translated using Weblate (Norwegian Nynorsk)

Currently translated at 94.0% (63 of 67 strings)

Translation: Habitica/Messages
Translate-URL: https://translate.habitica.com/projects/habitica/messages/nn/

Translated using Weblate (Malay)

Currently translated at 94.0% (63 of 67 strings)

Translation: Habitica/Messages
Translate-URL: https://translate.habitica.com/projects/habitica/messages/ms/

Translated using Weblate (Marathi)

Currently translated at 94.0% (63 of 67 strings)

Translation: Habitica/Messages
Translate-URL: https://translate.habitica.com/projects/habitica/messages/mr/

Translated using Weblate (Mongolian)

Currently translated at 94.0% (63 of 67 strings)

Translation: Habitica/Messages
Translate-URL: https://translate.habitica.com/projects/habitica/messages/mn/

Translated using Weblate (Malayalam)

Currently translated at 94.0% (63 of 67 strings)

Translation: Habitica/Messages
Translate-URL: https://translate.habitica.com/projects/habitica/messages/ml/

Translated using Weblate (Macedonian)

Currently translated at 94.0% (63 of 67 strings)

Translation: Habitica/Messages
Translate-URL: https://translate.habitica.com/projects/habitica/messages/mk/

Translated using Weblate (Latvian)

Currently translated at 94.0% (63 of 67 strings)

Translation: Habitica/Messages
Translate-URL: https://translate.habitica.com/projects/habitica/messages/lv/

Translated using Weblate (Lithuanian)

Currently translated at 94.0% (63 of 67 strings)

Translation: Habitica/Messages
Translate-URL: https://translate.habitica.com/projects/habitica/messages/lt/

Translated using Weblate (Lingala)

Currently translated at 94.0% (63 of 67 strings)

Translation: Habitica/Messages
Translate-URL: https://translate.habitica.com/projects/habitica/messages/ln/

Translated using Weblate (Kurdish)

Currently translated at 94.0% (63 of 67 strings)

Translation: Habitica/Messages
Translate-URL: https://translate.habitica.com/projects/habitica/messages/ku_IQ/

Translated using Weblate (Javanese)

Currently translated at 94.0% (63 of 67 strings)

Translation: Habitica/Messages
Translate-URL: https://translate.habitica.com/projects/habitica/messages/jv/

Translated using Weblate (Lojban)

Currently translated at 94.0% (63 of 67 strings)

Translation: Habitica/Messages
Translate-URL: https://translate.habitica.com/projects/habitica/messages/jbo/

Translated using Weblate (Icelandic)

Currently translated at 94.0% (63 of 67 strings)

Translation: Habitica/Messages
Translate-URL: https://translate.habitica.com/projects/habitica/messages/is/

Translated using Weblate (Croatian)

Currently translated at 94.0% (63 of 67 strings)

Translation: Habitica/Messages
Translate-URL: https://translate.habitica.com/projects/habitica/messages/hr/

Translated using Weblate (Hindi)

Currently translated at 94.0% (63 of 67 strings)

Translation: Habitica/Messages
Translate-URL: https://translate.habitica.com/projects/habitica/messages/hi/

Translated using Weblate (Hawaiian)

Currently translated at 94.0% (63 of 67 strings)

Translation: Habitica/Messages
Translate-URL: https://translate.habitica.com/projects/habitica/messages/haw/

Translated using Weblate (Galician)

Currently translated at 94.0% (63 of 67 strings)

Translation: Habitica/Messages
Translate-URL: https://translate.habitica.com/projects/habitica/messages/gl/

Translated using Weblate (Irish)

Currently translated at 94.0% (63 of 67 strings)

Translation: Habitica/Messages
Translate-URL: https://translate.habitica.com/projects/habitica/messages/ga/

Translated using Weblate (Frisian)

Currently translated at 94.0% (63 of 67 strings)

Translation: Habitica/Messages
Translate-URL: https://translate.habitica.com/projects/habitica/messages/fy/

Translated using Weblate (Filipino)

Currently translated at 100.0% (67 of 67 strings)

Translation: Habitica/Messages
Translate-URL: https://translate.habitica.com/projects/habitica/messages/fil/

Translated using Weblate (Finnish)

Currently translated at 94.0% (63 of 67 strings)

Translation: Habitica/Messages
Translate-URL: https://translate.habitica.com/projects/habitica/messages/fi/

Translated using Weblate (Persian)

Currently translated at 94.0% (63 of 67 strings)

Translation: Habitica/Messages
Translate-URL: https://translate.habitica.com/projects/habitica/messages/fa/

Translated using Weblate (Estonian)

Currently translated at 94.0% (63 of 67 strings)

Translation: Habitica/Messages
Translate-URL: https://translate.habitica.com/projects/habitica/messages/et/

Translated using Weblate (Esperanto)

Currently translated at 94.0% (63 of 67 strings)

Translation: Habitica/Messages
Translate-URL: https://translate.habitica.com/projects/habitica/messages/eo/

Translated using Weblate (Greek)

Currently translated at 94.0% (63 of 67 strings)

Translation: Habitica/Messages
Translate-URL: https://translate.habitica.com/projects/habitica/messages/el/

Translated using Weblate (Catalan)

Currently translated at 94.0% (63 of 67 strings)

Translation: Habitica/Messages
Translate-URL: https://translate.habitica.com/projects/habitica/messages/ca/

Translated using Weblate (Bosnian)

Currently translated at 94.0% (63 of 67 strings)

Translation: Habitica/Messages
Translate-URL: https://translate.habitica.com/projects/habitica/messages/bs/

Translated using Weblate (Bengali)

Currently translated at 94.0% (63 of 67 strings)

Translation: Habitica/Messages
Translate-URL: https://translate.habitica.com/projects/habitica/messages/bn/

Translated using Weblate (Belarusian)

Currently translated at 94.0% (63 of 67 strings)

Translation: Habitica/Messages
Translate-URL: https://translate.habitica.com/projects/habitica/messages/be/

Translated using Weblate (Arabic)

Currently translated at 94.0% (63 of 67 strings)

Translation: Habitica/Messages
Translate-URL: https://translate.habitica.com/projects/habitica/messages/ar/

Translated using Weblate (Afrikaans)

Currently translated at 94.0% (63 of 67 strings)

Translation: Habitica/Messages
Translate-URL: https://translate.habitica.com/projects/habitica/messages/af/

Translated using Weblate (Acholi)

Currently translated at 94.0% (63 of 67 strings)

Translation: Habitica/Messages
Translate-URL: https://translate.habitica.com/projects/habitica/messages/ach/

Translated using Weblate (Arabic)

Currently translated at 100.0% (31 of 31 strings)

Translation: Habitica/Maintenance
Translate-URL: https://translate.habitica.com/projects/habitica/maintenance/ar/

Translated using Weblate (Chinese (Hong Kong))

Currently translated at 95.5% (471 of 493 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/zh_Hant_HK/

Translated using Weblate (Vietnamese)

Currently translated at 97.3% (480 of 493 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/vi/

Translated using Weblate (Urdu (Pakistan))

Currently translated at 95.5% (471 of 493 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/ur_PK/

Translated using Weblate (Klingon)

Currently translated at 95.5% (471 of 493 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/tlh/

Translated using Weblate (Tagalog)

Currently translated at 95.5% (471 of 493 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/tl/

Translated using Weblate (Thai)

Currently translated at 95.5% (471 of 493 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/th/

Translated using Weblate (Tamil)

Currently translated at 95.9% (473 of 493 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/ta/

Translated using Weblate (Swahili)

Currently translated at 95.5% (471 of 493 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/sw/

Translated using Weblate (Sundanese)

Currently translated at 95.5% (471 of 493 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/su/

Translated using Weblate (Slovenian)

Currently translated at 95.5% (471 of 493 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/sl/

Translated using Weblate (Sinhala)

Currently translated at 95.5% (471 of 493 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/si/

Translated using Weblate (Scots)

Currently translated at 95.5% (471 of 493 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/sco/

Translated using Weblate (Norwegian Bokmål)

Currently translated at 95.5% (471 of 493 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/nb_NO/

Translated using Weblate (Norwegian Nynorsk)

Currently translated at 95.5% (471 of 493 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/nn/

Translated using Weblate (Malay)

Currently translated at 95.5% (471 of 493 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/ms/

Translated using Weblate (Marathi)

Currently translated at 95.5% (471 of 493 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/mr/

Translated using Weblate (Mongolian)

Currently translated at 95.5% (471 of 493 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/mn/

Translated using Weblate (Malayalam)

Currently translated at 95.5% (471 of 493 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/ml/

Translated using Weblate (Macedonian)

Currently translated at 95.5% (471 of 493 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/mk/

Translated using Weblate (Latvian)

Currently translated at 95.5% (471 of 493 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/lv/

Translated using Weblate (Lithuanian)

Currently translated at 95.5% (471 of 493 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/lt/

Translated using Weblate (Lingala)

Currently translated at 95.5% (471 of 493 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/ln/

Translated using Weblate (Kurdish)

Currently translated at 95.5% (471 of 493 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/ku_IQ/

Translated using Weblate (Korean)

Currently translated at 96.1% (474 of 493 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/ko/

Translated using Weblate (Javanese)

Currently translated at 95.5% (471 of 493 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/jv/

Translated using Weblate (Lojban)

Currently translated at 95.5% (471 of 493 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/jbo/

Translated using Weblate (Icelandic)

Currently translated at 95.5% (471 of 493 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/is/

Translated using Weblate (Croatian)

Currently translated at 95.5% (471 of 493 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/hr/

Translated using Weblate (Hindi)

Currently translated at 95.5% (471 of 493 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/hi/

Translated using Weblate (Hawaiian)

Currently translated at 95.5% (471 of 493 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/haw/

Translated using Weblate (Galician)

Currently translated at 95.5% (471 of 493 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/gl/

Translated using Weblate (Irish)

Currently translated at 95.5% (471 of 493 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/ga/

Translated using Weblate (Frisian)

Currently translated at 95.5% (471 of 493 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/fy/

Translated using Weblate (Filipino)

Currently translated at 98.5% (486 of 493 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/fil/

Translated using Weblate (Finnish)

Currently translated at 95.5% (471 of 493 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/fi/

Translated using Weblate (Persian)

Currently translated at 95.5% (471 of 493 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/fa/

Translated using Weblate (Estonian)

Currently translated at 95.5% (471 of 493 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/et/

Translated using Weblate (Esperanto)

Currently translated at 95.5% (471 of 493 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/eo/

Translated using Weblate (Greek)

Currently translated at 95.5% (471 of 493 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/el/

Translated using Weblate (Catalan)

Currently translated at 95.5% (471 of 493 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/ca/

Translated using Weblate (Bosnian)

Currently translated at 95.5% (471 of 493 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/bs/

Translated using Weblate (Bengali)

Currently translated at 95.5% (471 of 493 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/bn/

Translated using Weblate (Belarusian)

Currently translated at 95.5% (471 of 493 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/be/

Translated using Weblate (Arabic)

Currently translated at 95.5% (471 of 493 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/ar/

Translated using Weblate (Afrikaans)

Currently translated at 95.5% (471 of 493 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/af/

Translated using Weblate (Acholi)

Currently translated at 95.5% (471 of 493 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/ach/

Translated using Weblate (Malay)

Currently translated at 97.3% (290 of 298 strings)

Translation: Habitica/Generic
Translate-URL: https://translate.habitica.com/projects/habitica/generic/ms/

Translated using Weblate (Arabic)

Currently translated at 80.5% (1702 of 2113 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ar/

Translated using Weblate (Persian)

Currently translated at 100.0% (70 of 70 strings)

Translation: Habitica/Contrib
Translate-URL: https://translate.habitica.com/projects/habitica/contrib/fa/

Translated using Weblate (Arabic)

Currently translated at 100.0% (70 of 70 strings)

Translation: Habitica/Contrib
Translate-URL: https://translate.habitica.com/projects/habitica/contrib/ar/

Translated using Weblate (Korean)

Currently translated at 86.1% (310 of 360 strings)

Translation: Habitica/Content
Translate-URL: https://translate.habitica.com/projects/habitica/content/ko/

Translated using Weblate (Arabic)

Currently translated at 85.2% (307 of 360 strings)

Translation: Habitica/Content
Translate-URL: https://translate.habitica.com/projects/habitica/content/ar/

Translated using Weblate (Galician)

Currently translated at 91.9% (194 of 211 strings)

Translation: Habitica/Settings
Translate-URL: https://translate.habitica.com/projects/habitica/settings/gl/

Translated using Weblate (Swedish)

Currently translated at 98.2% (56 of 57 strings)

Translation: Habitica/Spells
Translate-URL: https://translate.habitica.com/projects/habitica/spells/sv/

Translated using Weblate (Hungarian)

Currently translated at 96.4% (55 of 57 strings)

Translation: Habitica/Spells
Translate-URL: https://translate.habitica.com/projects/habitica/spells/hu/

Translated using Weblate (English (Pirate))

Currently translated at 98.2% (56 of 57 strings)

Translation: Habitica/Spells
Translate-URL: https://translate.habitica.com/projects/habitica/spells/en@pirate/

Translated using Weblate (Danish)

Currently translated at 98.2% (56 of 57 strings)

Translation: Habitica/Spells
Translate-URL: https://translate.habitica.com/projects/habitica/spells/da/

Translated using Weblate (Slovak)

Currently translated at 95.1% (136 of 143 strings)

Translation: Habitica/Pets
Translate-URL: https://translate.habitica.com/projects/habitica/pets/sk/

Translated using Weblate (Hebrew)

Currently translated at 93.7% (134 of 143 strings)

Translation: Habitica/Pets
Translate-URL: https://translate.habitica.com/projects/habitica/pets/he/

Translated using Weblate (Ukrainian)

Currently translated at 95.5% (64 of 67 strings)

Translation: Habitica/Messages
Translate-URL: https://translate.habitica.com/projects/habitica/messages/uk/

Translated using Weblate (Hebrew)

Currently translated at 94.0% (63 of 67 strings)

Translation: Habitica/Messages
Translate-URL: https://translate.habitica.com/projects/habitica/messages/he/

Translated using Weblate (Spanish)

Currently translated at 100.0% (67 of 67 strings)

Translation: Habitica/Messages
Translate-URL: https://translate.habitica.com/projects/habitica/messages/es/

Translated using Weblate (Turkish)

Currently translated at 95.5% (471 of 493 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/tr/

Translated using Weblate (Hebrew)

Currently translated at 92.9% (458 of 493 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/he/

Translated using Weblate (Spanish)

Currently translated at 100.0% (493 of 493 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/es/

Translated using Weblate (Japanese)

Currently translated at 86.7% (1833 of 2113 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ja/

Translated using Weblate (Italian)

Currently translated at 82.6% (1746 of 2113 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/it/

Translated using Weblate (Hebrew)

Currently translated at 96.9% (318 of 328 strings)

Translation: Habitica/Front
Translate-URL: https://translate.habitica.com/projects/habitica/front/he/

Translated using Weblate (Chinese (Simplified))

Currently translated at 99.8% (701 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/zh_Hans/

Translated using Weblate (Italian)

Currently translated at 94.1% (661 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/it/

Translated using Weblate (Russian)

Currently translated at 100.0% (83 of 83 strings)

Translation: Habitica/Achievements
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/ru/

Translated using Weblate (Hungarian)

Currently translated at 34.9% (29 of 83 strings)

Translation: Habitica/Achievements
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/hu/

Translated using Weblate (Czech)

Currently translated at 68.6% (57 of 83 strings)

Translation: Habitica/Achievements
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/cs/

Translated using Weblate (Czech)

Currently translated at 68.6% (57 of 83 strings)

Translation: Habitica/Achievements
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/cs/

Translated using Weblate (Italian)

Currently translated at 81.8% (1730 of 2113 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/it/

Translated using Weblate (Italian)

Currently translated at 81.8% (1730 of 2113 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/it/

Translated using Weblate (Italian)

Currently translated at 81.7% (1728 of 2113 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/it/

Translated using Weblate (Latin)

Currently translated at 31.4% (45 of 143 strings)

Translation: Habitica/Pets
Translate-URL: https://translate.habitica.com/projects/habitica/pets/la/

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (28 of 28 strings)

Translation: Habitica/Rebirth
Translate-URL: https://translate.habitica.com/projects/habitica/rebirth/vi/

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (67 of 67 strings)

Translation: Habitica/Messages
Translate-URL: https://translate.habitica.com/projects/habitica/messages/vi/

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (298 of 298 strings)

Translation: Habitica/Generic
Translate-URL: https://translate.habitica.com/projects/habitica/generic/vi/

Translated using Weblate (Vietnamese)

Currently translated at 82.2% (1737 of 2113 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/vi/

Translated using Weblate (Vietnamese)

Currently translated at 96.2% (203 of 211 strings)

Translation: Habitica/Settings
Translate-URL: https://translate.habitica.com/projects/habitica/settings/vi/

Translated using Weblate (Japanese)

Currently translated at 100.0% (250 of 250 strings)

Translation: Habitica/Subscriber
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/ja/

Translated using Weblate (Italian)

Currently translated at 100.0% (250 of 250 strings)

Translation: Habitica/Subscriber
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/it/

Translated using Weblate (Japanese)

Currently translated at 100.0% (57 of 57 strings)

Translation: Habitica/Spells
Translate-URL: https://translate.habitica.com/projects/habitica/spells/ja/

Translated using Weblate (Japanese)

Currently translated at 86.6% (1830 of 2113 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ja/

Translated using Weblate (Spanish)

Currently translated at 89.5% (1892 of 2113 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/es/

Translated using Weblate (Italian)

Currently translated at 92.0% (646 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/it/

Translated using Weblate (Japanese)

Currently translated at 100.0% (83 of 83 strings)

Translation: Habitica/Achievements
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/ja/

Translated using Weblate (Italian)

Currently translated at 94.4% (236 of 250 strings)

Translation: Habitica/Subscriber
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/it/

Translated using Weblate (Vietnamese)

Currently translated at 81.1% (1715 of 2113 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/vi/

Translated using Weblate (Portuguese (Brazil))

Currently translated at 98.5% (2082 of 2113 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/pt_BR/

Translated using Weblate (French)

Currently translated at 100.0% (250 of 250 strings)

Translation: Habitica/Subscriber
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/fr/

Translated using Weblate (French)

Currently translated at 100.0% (57 of 57 strings)

Translation: Habitica/Spells
Translate-URL: https://translate.habitica.com/projects/habitica/spells/fr/

Translated using Weblate (French)

Currently translated at 100.0% (2113 of 2113 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/fr/

Translated using Weblate (French)

Currently translated at 100.0% (213 of 213 strings)

Translation: Habitica/Tasks
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/fr/

Translated using Weblate (French)

Currently translated at 100.0% (2113 of 2113 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/fr/

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (328 of 328 strings)

Translation: Habitica/Front
Translate-URL: https://translate.habitica.com/projects/habitica/front/vi/

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (57 of 57 strings)

Translation: Habitica/Spells
Translate-URL: https://translate.habitica.com/projects/habitica/spells/vi/

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (56 of 56 strings)

Translation: Habitica/Faq
Translate-URL: https://translate.habitica.com/projects/habitica/faq/vi/

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (536 of 536 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/vi/

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (6 of 6 strings)

Translation: Habitica/Inventory
Translate-URL: https://translate.habitica.com/projects/habitica/inventory/vi/

Translated using Weblate (Vietnamese)

Currently translated at 80.8% (1708 of 2113 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/vi/

Translated using Weblate (Japanese)

Currently translated at 100.0% (213 of 213 strings)

Translation: Habitica/Tasks
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/ja/

Translated using Weblate (Italian)

Currently translated at 100.0% (213 of 213 strings)

Translation: Habitica/Tasks
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/it/

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (250 of 250 strings)

Translation: Habitica/Subscriber
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/pt_BR/

Translated using Weblate (Italian)

Currently translated at 90.0% (225 of 250 strings)

Translation: Habitica/Subscriber
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/it/

Translated using Weblate (Italian)

Currently translated at 90.0% (225 of 250 strings)

Translation: Habitica/Subscriber
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/it/

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (57 of 57 strings)

Translation: Habitica/Spells
Translate-URL: https://translate.habitica.com/projects/habitica/spells/pt_BR/

Translated using Weblate (Italian)

Currently translated at 100.0% (57 of 57 strings)

Translation: Habitica/Spells
Translate-URL: https://translate.habitica.com/projects/habitica/spells/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (140 of 140 strings)

Translation: Habitica/Quests
Translate-URL: https://translate.habitica.com/projects/habitica/quests/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (28 of 28 strings)

Translation: Habitica/Rebirth
Translate-URL: https://translate.habitica.com/projects/habitica/rebirth/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (173 of 173 strings)

Translation: Habitica/Npc
Translate-URL: https://translate.habitica.com/projects/habitica/npc/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (8 of 8 strings)

Translation: Habitica/Overview
Translate-URL: https://translate.habitica.com/projects/habitica/overview/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (67 of 67 strings)

Translation: Habitica/Messages
Translate-URL: https://translate.habitica.com/projects/habitica/messages/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (31 of 31 strings)

Translation: Habitica/Maintenance
Translate-URL: https://translate.habitica.com/projects/habitica/maintenance/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (185 of 185 strings)

Translation: Habitica/Limited
Translate-URL: https://translate.habitica.com/projects/habitica/limited/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (27 of 27 strings)

Translation: Habitica/Loginincentives
Translate-URL: https://translate.habitica.com/projects/habitica/loginincentives/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (493 of 493 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/it/

Translated using Weblate (Italian)

Currently translated at 81.6% (1725 of 2113 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (298 of 298 strings)

Translation: Habitica/Generic
Translate-URL: https://translate.habitica.com/projects/habitica/generic/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (328 of 328 strings)

Translation: Habitica/Front
Translate-URL: https://translate.habitica.com/projects/habitica/front/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (56 of 56 strings)

Translation: Habitica/Faq
Translate-URL: https://translate.habitica.com/projects/habitica/faq/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (63 of 63 strings)

Translation: Habitica/Defaulttasks
Translate-URL: https://translate.habitica.com/projects/habitica/defaulttasks/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (15 of 15 strings)

Translation: Habitica/Death
Translate-URL: https://translate.habitica.com/projects/habitica/death/it/

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (702 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/pt_BR/

Translated using Weblate (Italian)

Currently translated at 90.3% (634 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (124 of 124 strings)

Translation: Habitica/Communityguidelines
Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (360 of 360 strings)

Translation: Habitica/Content
Translate-URL: https://translate.habitica.com/projects/habitica/content/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (360 of 360 strings)

Translation: Habitica/Content
Translate-URL: https://translate.habitica.com/projects/habitica/content/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (78 of 78 strings)

Translation: Habitica/Contrib
Translate-URL: https://translate.habitica.com/projects/habitica/contrib/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (230 of 230 strings)

Translation: Habitica/Character
Translate-URL: https://translate.habitica.com/projects/habitica/character/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (134 of 134 strings)

Translation: Habitica/Challenge
Translate-URL: https://translate.habitica.com/projects/habitica/challenge/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (536 of 536 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (83 of 83 strings)

Translation: Habitica/Achievements
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (211 of 211 strings)

Translation: Habitica/Settings
Translate-URL: https://translate.habitica.com/projects/habitica/settings/it/

Translated using Weblate (Italian)

Currently translated at 98.4% (323 of 328 strings)

Translation: Habitica/Front
Translate-URL: https://translate.habitica.com/projects/habitica/front/it/

Translated using Weblate (Chinese (Simplified))

Currently translated at 99.8% (701 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/zh_Hans/

Translated using Weblate (Spanish)

Currently translated at 97.3% (522 of 536 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/es/

Translated using Weblate (German)

Currently translated at 100.0% (213 of 213 strings)

Translation: Habitica/Tasks
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/de/

Translated using Weblate (Russian)

Currently translated at 95.2% (238 of 250 strings)

Translation: Habitica/Subscriber
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/ru/

Translated using Weblate (German)

Currently translated at 100.0% (250 of 250 strings)

Translation: Habitica/Subscriber
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/de/

Translated using Weblate (German)

Currently translated at 100.0% (57 of 57 strings)

Translation: Habitica/Spells
Translate-URL: https://translate.habitica.com/projects/habitica/spells/de/

Translated using Weblate (German)

Currently translated at 100.0% (185 of 185 strings)

Translation: Habitica/Limited
Translate-URL: https://translate.habitica.com/projects/habitica/limited/de/

Translated using Weblate (Russian)

Currently translated at 96.8% (2047 of 2113 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ru/

Translated using Weblate (German)

Currently translated at 99.1% (2095 of 2113 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/de/
2020-07-02 21:05:43 +02:00
Sabe Jones
e36a91a5c7 4.147.2 2020-07-02 13:58:50 -05:00
Matteo Pagliazzi
ea623936bf apidoc: upgrade and add links to wiki 2020-07-02 18:19:15 +02:00
Sabe Jones
5a7e1cbc5a Merge branch 'release' into develop 2020-07-01 16:37:22 -05:00
Sabe Jones
0303f0b610 4.147.1 2020-07-01 16:36:57 -05:00
Sabe Jones
fb6eacf8f9 chore(news): Bailey 2020-07-01 16:36:51 -05:00
Matteo Pagliazzi
3d5630d5a2 chore: update sub-deps and fix lint 2020-07-01 18:20:18 +02:00
Sabe Jones
cbce80e926 fix(sorting): remove leading article from umbrella 2020-06-30 11:55:01 -05:00
Melior
b9d42bf0bc Translated using Weblate (Russian)
Currently translated at 100.0% (213 of 213 strings)

Translation: Habitica/Tasks
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/ru/

Translated using Weblate (Portuguese (Brazil))

Currently translated at 98.6% (2080 of 2109 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/pt_BR/

Translated using Weblate (Japanese)

Currently translated at 86.5% (1825 of 2109 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ja/

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (140 of 140 strings)

Translation: Habitica/Quests
Translate-URL: https://translate.habitica.com/projects/habitica/quests/pt_BR/

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (8 of 8 strings)

Translation: Habitica/Overview
Translate-URL: https://translate.habitica.com/projects/habitica/overview/pt_BR/

Translated using Weblate (Portuguese (Brazil))

Currently translated at 98.4% (2077 of 2109 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/pt_BR/

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (702 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/pt_BR/

Translated using Weblate (English)

Currently translated at 80.3% (45 of 56 strings)

Translation: Habitica/Faq
Translate-URL: https://translate.habitica.com/projects/habitica/faq/en@lolcat/

Translated using Weblate (English)

Currently translated at 100.0% (230 of 230 strings)

Translation: Habitica/Character
Translate-URL: https://translate.habitica.com/projects/habitica/character/en@lolcat/

Translated using Weblate (English)

Currently translated at 94.0% (63 of 67 strings)

Translation: Habitica/Messages
Translate-URL: https://translate.habitica.com/projects/habitica/messages/en@lolcat/

Translated using Weblate (English)

Currently translated at 95.3% (470 of 493 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/en@lolcat/

Translated using Weblate (English)

Currently translated at 97.3% (290 of 298 strings)

Translation: Habitica/Generic
Translate-URL: https://translate.habitica.com/projects/habitica/generic/en@lolcat/

Translated using Weblate (English)

Currently translated at 80.7% (1703 of 2109 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/en@lolcat/

Translated using Weblate (Italian)

Currently translated at 100.0% (57 of 57 strings)

Translation: Habitica/Spells
Translate-URL: https://translate.habitica.com/projects/habitica/spells/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (143 of 143 strings)

Translation: Habitica/Pets
Translate-URL: https://translate.habitica.com/projects/habitica/pets/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (8 of 8 strings)

Translation: Habitica/Overview
Translate-URL: https://translate.habitica.com/projects/habitica/overview/it/

Translated using Weblate (Portuguese (Brazil))

Currently translated at 98.4% (2076 of 2109 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/pt_BR/

Translated using Weblate (Italian)

Currently translated at 98.9% (295 of 298 strings)

Translation: Habitica/Generic
Translate-URL: https://translate.habitica.com/projects/habitica/generic/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (56 of 56 strings)

Translation: Habitica/Faq
Translate-URL: https://translate.habitica.com/projects/habitica/faq/it/

Translated using Weblate (Chinese (Simplified))

Currently translated at 99.8% (701 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/zh_Hans/

Translated using Weblate (Italian)

Currently translated at 100.0% (124 of 124 strings)

Translation: Habitica/Communityguidelines
Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/it/

Translated using Weblate (Portuguese)

Currently translated at 100.0% (70 of 70 strings)

Translation: Habitica/Contrib
Translate-URL: https://translate.habitica.com/projects/habitica/contrib/pt/

Translated using Weblate (Italian)

Currently translated at 100.0% (230 of 230 strings)

Translation: Habitica/Character
Translate-URL: https://translate.habitica.com/projects/habitica/character/it/

Translated using Weblate (Portuguese)

Currently translated at 100.0% (134 of 134 strings)

Translation: Habitica/Challenge
Translate-URL: https://translate.habitica.com/projects/habitica/challenge/pt/

Translated using Weblate (Italian)

Currently translated at 89.3% (479 of 536 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/it/

Translated using Weblate (Italian)

Currently translated at 65.0% (54 of 83 strings)

Translation: Habitica/Achievements
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/it/

Translated using Weblate (Italian)

Currently translated at 65.0% (54 of 83 strings)

Translation: Habitica/Achievements
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (211 of 211 strings)

Translation: Habitica/Settings
Translate-URL: https://translate.habitica.com/projects/habitica/settings/it/

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (213 of 213 strings)

Translation: Habitica/Tasks
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/pt_BR/

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (249 of 249 strings)

Translation: Habitica/Subscriber
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/pt_BR/

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (28 of 28 strings)

Translation: Habitica/Rebirth
Translate-URL: https://translate.habitica.com/projects/habitica/rebirth/pt_BR/

Translated using Weblate (Chinese (Simplified))

Currently translated at 99.8% (701 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/zh_Hans/

Translated using Weblate (Portuguese (Brazil))

Currently translated at 92.9% (198 of 213 strings)

Translation: Habitica/Tasks
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/pt_BR/

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (173 of 173 strings)

Translation: Habitica/Npc
Translate-URL: https://translate.habitica.com/projects/habitica/npc/pt_BR/

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (185 of 185 strings)

Translation: Habitica/Limited
Translate-URL: https://translate.habitica.com/projects/habitica/limited/pt_BR/

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (328 of 328 strings)

Translation: Habitica/Front
Translate-URL: https://translate.habitica.com/projects/habitica/front/pt_BR/

Translated using Weblate (Portuguese (Brazil))

Currently translated at 98.8% (694 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/pt_BR/

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (360 of 360 strings)

Translation: Habitica/Content
Translate-URL: https://translate.habitica.com/projects/habitica/content/pt_BR/

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (211 of 211 strings)

Translation: Habitica/Settings
Translate-URL: https://translate.habitica.com/projects/habitica/settings/pt_BR/

Translated using Weblate (Portuguese (Brazil))

Currently translated at 98.7% (324 of 328 strings)

Translation: Habitica/Front
Translate-URL: https://translate.habitica.com/projects/habitica/front/pt_BR/

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (56 of 56 strings)

Translation: Habitica/Faq
Translate-URL: https://translate.habitica.com/projects/habitica/faq/pt_BR/

Translated using Weblate (English)

Currently translated at 1.2% (4 of 328 strings)

Translation: Habitica/Front
Translate-URL: https://translate.habitica.com/projects/habitica/front/en@lolcat/

Translated using Weblate (English)

Currently translated at 100.0% (57 of 57 strings)

Translation: Habitica/Spells
Translate-URL: https://translate.habitica.com/projects/habitica/spells/en@lolcat/

Translated using Weblate (English)

Currently translated at 91.9% (159 of 173 strings)

Translation: Habitica/Npc
Translate-URL: https://translate.habitica.com/projects/habitica/npc/en@lolcat/

Translated using Weblate (English)

Currently translated at 80.3% (45 of 56 strings)

Translation: Habitica/Faq
Translate-URL: https://translate.habitica.com/projects/habitica/faq/en@lolcat/

Translated using Weblate (English)

Currently translated at 100.0% (230 of 230 strings)

Translation: Habitica/Character
Translate-URL: https://translate.habitica.com/projects/habitica/character/en@lolcat/

Translated using Weblate (English)

Currently translated at 89.6% (191 of 213 strings)

Translation: Habitica/Tasks
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/en@lolcat/

Translated using Weblate (English)

Currently translated at 79.5% (198 of 249 strings)

Translation: Habitica/Subscriber
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/en@lolcat/

Translated using Weblate (English)

Currently translated at 96.4% (27 of 28 strings)

Translation: Habitica/Rebirth
Translate-URL: https://translate.habitica.com/projects/habitica/rebirth/en@lolcat/

Translated using Weblate (English)

Currently translated at 85.7% (120 of 140 strings)

Translation: Habitica/Quests
Translate-URL: https://translate.habitica.com/projects/habitica/quests/en@lolcat/

Translated using Weblate (English)

Currently translated at 100.0% (31 of 31 strings)

Translation: Habitica/Maintenance
Translate-URL: https://translate.habitica.com/projects/habitica/maintenance/en@lolcat/

Translated using Weblate (English)

Currently translated at 95.3% (470 of 493 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/en@lolcat/

Translated using Weblate (English)

Currently translated at 97.3% (290 of 298 strings)

Translation: Habitica/Generic
Translate-URL: https://translate.habitica.com/projects/habitica/generic/en@lolcat/

Translated using Weblate (English)

Currently translated at 80.7% (1703 of 2109 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/en@lolcat/

Translated using Weblate (English)

Currently translated at 100.0% (63 of 63 strings)

Translation: Habitica/Defaulttasks
Translate-URL: https://translate.habitica.com/projects/habitica/defaulttasks/en@lolcat/

Translated using Weblate (English)

Currently translated at 100.0% (124 of 124 strings)

Translation: Habitica/Communityguidelines
Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/en@lolcat/

Translated using Weblate (Russian)

Currently translated at 98.7% (82 of 83 strings)

Translation: Habitica/Achievements
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/ru/

Translated using Weblate (Japanese)

Currently translated at 100.0% (249 of 249 strings)

Translation: Habitica/Subscriber
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/ja/

Translated using Weblate (Japanese)

Currently translated at 100.0% (185 of 185 strings)

Translation: Habitica/Limited
Translate-URL: https://translate.habitica.com/projects/habitica/limited/ja/

Translated using Weblate (Japanese)

Currently translated at 86.3% (1821 of 2109 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ja/

Translated using Weblate (Chinese (Simplified))

Currently translated at 99.8% (701 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/zh_Hans/

Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (57 of 57 strings)

Translation: Habitica/Spells
Translate-URL: https://translate.habitica.com/projects/habitica/spells/zh_Hant/

Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (185 of 185 strings)

Translation: Habitica/Limited
Translate-URL: https://translate.habitica.com/projects/habitica/limited/zh_Hant/

Translated using Weblate (Chinese (Traditional))

Currently translated at 98.9% (2087 of 2109 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/zh_Hant/

Translated using Weblate (Chinese (Traditional))

Currently translated at 98.7% (693 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/zh_Hant/

Translated using Weblate (Chinese (Traditional))

Currently translated at 98.2% (56 of 57 strings)

Translation: Habitica/Spells
Translate-URL: https://translate.habitica.com/projects/habitica/spells/zh_Hant/

Translated using Weblate (Chinese (Traditional))

Currently translated at 96.4% (55 of 57 strings)

Translation: Habitica/Spells
Translate-URL: https://translate.habitica.com/projects/habitica/spells/zh_Hant/

Translated using Weblate (English)

Currently translated at 78.5% (44 of 56 strings)

Translation: Habitica/Faq
Translate-URL: https://translate.habitica.com/projects/habitica/faq/en@lolcat/

Translated using Weblate (English)

Currently translated at 99.1% (123 of 124 strings)

Translation: Habitica/Communityguidelines
Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/en@lolcat/

Translated using Weblate (English)

Currently translated at 92.4% (195 of 211 strings)

Translation: Habitica/Settings
Translate-URL: https://translate.habitica.com/projects/habitica/settings/en@lolcat/

Translated using Weblate (Belarusian)

Currently translated at 91.9% (194 of 211 strings)

Translation: Habitica/Settings
Translate-URL: https://translate.habitica.com/projects/habitica/settings/be/

Translated using Weblate (English (Pirate))

Currently translated at 78.5% (44 of 56 strings)

Translation: Habitica/Faq
Translate-URL: https://translate.habitica.com/projects/habitica/faq/en@pirate/

Translated using Weblate (English (Pirate))

Currently translated at 99.1% (123 of 124 strings)

Translation: Habitica/Communityguidelines
Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/en@pirate/

Translated using Weblate (Ukrainian)

Currently translated at 91.9% (194 of 211 strings)

Translation: Habitica/Settings
Translate-URL: https://translate.habitica.com/projects/habitica/settings/uk/

Translated using Weblate (English (Pirate))

Currently translated at 99.0% (209 of 211 strings)

Translation: Habitica/Settings
Translate-URL: https://translate.habitica.com/projects/habitica/settings/en@pirate/

Translated using Weblate (Bulgarian)

Currently translated at 99.5% (210 of 211 strings)

Translation: Habitica/Settings
Translate-URL: https://translate.habitica.com/projects/habitica/settings/bg/

Translated using Weblate (Portuguese)

Currently translated at 81.9% (1728 of 2109 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/pt/

Translated using Weblate (Dutch)

Currently translated at 97.5% (2058 of 2109 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/nl/

Translated using Weblate (Japanese)

Currently translated at 86.2% (1819 of 2109 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ja/

Translated using Weblate (Chinese (Simplified))

Currently translated at 99.8% (701 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/zh_Hans/

Translated using Weblate (Russian)

Currently translated at 100.0% (126 of 126 strings)

Translation: Habitica/Communityguidelines
Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/ru/

Translated using Weblate (Russian)

Currently translated at 97.5% (81 of 83 strings)

Translation: Habitica/Achievements
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/ru/
2020-06-29 22:54:56 +02:00
Sabe Jones
40cadba7bf Merge branch 'release' into develop 2020-06-29 15:15:54 -05:00
Sabe Jones
57446c3041 4.147.0 2020-06-29 15:15:28 -05:00
Sabe Jones
ee2a5fe6b3 chore(sprites): compile 2020-06-29 15:15:19 -05:00
Sabe Jones
2b166af82a feat(content): July 2020 subscriber set
Also refactor repetitive subscriber item code
2020-06-29 15:15:11 -05:00
wwyl1234
2c096d5feb Changed flex box direction to column. Changed other css properties such that tag names do not collapse when view width is changed on the browser. (#12332)
Co-authored-by: Winnie Lam <winnielam@Winnies-MacBook-Air.local>
2020-06-29 18:21:57 +02:00
Matteo Pagliazzi
b2833ac4a2 fix(challenge): sync user when joining a challenge - make sure new tag is synced immediately 2020-06-29 17:05:37 +02:00
Alexandrea Beh
4f2656f8f6 Issue 12220 stealth tooltip show protected dailies (#12255)
* Issue 12220 - Show dailies protected by Stealth

A feature on the old site showed the number of
dailies protected by casting Stealth. This is
now showing again in the Stealth tooltip. The
skillNotes section was not being called. It
adds additional skill-specific info: the dailies
avoided by stealth, whether stealth no longer
needs to be cast (dailies already avoided) and
whether frost is no longer useful to cast.

Speculation: the spellDisabled method had some
commented out code regarding Stealth, which
may have broken due to a change in how dailies
are referenced. I have fixed this line, so it
seemed alright to keep the entirety of the
skillNotes function as it had been. However,
this includes more than just showing dailies
in the tooltip.

Behavior changes:
- tooltip shows dailies avoided for Stealth
- tooltip shows maxed out message for
Stealth when all dailies are covered
- tooltip shows frost already cast message
when frost has already been cast

* clean up conditions in skillNotes

* use future tense for rogue stealth dailies tooltip

* use getter for accurate task length

* consider stealth disabled based on incomplete dailies due, not all dailies

* Issue 12220 - Show dailies protected by Stealth

A feature on the old site showed the number of
dailies protected by casting Stealth. This is
now showing again in the Stealth tooltip. The
skillNotes section was not being called. It
adds additional skill-specific info: the dailies
avoided by stealth, whether stealth no longer
needs to be cast (dailies already avoided) and
whether frost is no longer useful to cast.

Speculation: the spellDisabled method had some
commented out code regarding Stealth, which
may have broken due to a change in how dailies
are referenced. I have fixed this line, so it
seemed alright to keep the entirety of the
skillNotes function as it had been. However,
this includes more than just showing dailies
in the tooltip.

Behavior changes:
- tooltip shows dailies avoided for Stealth
- tooltip shows maxed out message for
Stealth when all dailies are covered
- tooltip shows frost already cast message
when frost has already been cast

* factor out stealthBuffsToAdd for casting stealth + tooltip previewing dailies avoided

* fix merge conflict
2020-06-29 16:45:00 +02:00
Alexandrea Beh
da60f44356 Issue 12220 stealth tooltip show protected dailies (#12255)
* Issue 12220 - Show dailies protected by Stealth

A feature on the old site showed the number of
dailies protected by casting Stealth. This is
now showing again in the Stealth tooltip. The
skillNotes section was not being called. It
adds additional skill-specific info: the dailies
avoided by stealth, whether stealth no longer
needs to be cast (dailies already avoided) and
whether frost is no longer useful to cast.

Speculation: the spellDisabled method had some
commented out code regarding Stealth, which
may have broken due to a change in how dailies
are referenced. I have fixed this line, so it
seemed alright to keep the entirety of the
skillNotes function as it had been. However,
this includes more than just showing dailies
in the tooltip.

Behavior changes:
- tooltip shows dailies avoided for Stealth
- tooltip shows maxed out message for
Stealth when all dailies are covered
- tooltip shows frost already cast message
when frost has already been cast

* clean up conditions in skillNotes

* use future tense for rogue stealth dailies tooltip

* use getter for accurate task length

* consider stealth disabled based on incomplete dailies due, not all dailies

* Issue 12220 - Show dailies protected by Stealth

A feature on the old site showed the number of
dailies protected by casting Stealth. This is
now showing again in the Stealth tooltip. The
skillNotes section was not being called. It
adds additional skill-specific info: the dailies
avoided by stealth, whether stealth no longer
needs to be cast (dailies already avoided) and
whether frost is no longer useful to cast.

Speculation: the spellDisabled method had some
commented out code regarding Stealth, which
may have broken due to a change in how dailies
are referenced. I have fixed this line, so it
seemed alright to keep the entirety of the
skillNotes function as it had been. However,
this includes more than just showing dailies
in the tooltip.

Behavior changes:
- tooltip shows dailies avoided for Stealth
- tooltip shows maxed out message for
Stealth when all dailies are covered
- tooltip shows frost already cast message
when frost has already been cast

* factor out stealthBuffsToAdd for casting stealth + tooltip previewing dailies avoided

* fix merge conflict
2020-06-29 11:12:19 +02:00
dependabot-preview[bot]
911a44adec build(deps): bump @vue/cli-plugin-unit-mocha in /website/client (#12351)
Bumps [@vue/cli-plugin-unit-mocha](https://github.com/vuejs/vue-cli/tree/HEAD/packages/@vue/cli-plugin-unit-mocha) from 4.4.4 to 4.4.6.
- [Release notes](https://github.com/vuejs/vue-cli/releases)
- [Changelog](https://github.com/vuejs/vue-cli/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/vuejs/vue-cli/commits/v4.4.6/packages/@vue/cli-plugin-unit-mocha)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
Co-authored-by: Matteo Pagliazzi <matteopagliazzi@gmail.com>
2020-06-29 10:53:08 +02:00
dependabot-preview[bot]
e38a455922 build(deps): bump @vue/cli-plugin-eslint in /website/client (#12353)
Bumps [@vue/cli-plugin-eslint](https://github.com/vuejs/vue-cli/tree/HEAD/packages/@vue/cli-plugin-eslint) from 4.4.4 to 4.4.6.
- [Release notes](https://github.com/vuejs/vue-cli/releases)
- [Changelog](https://github.com/vuejs/vue-cli/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/vuejs/vue-cli/commits/v4.4.6/packages/@vue/cli-plugin-eslint)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
Co-authored-by: Matteo Pagliazzi <matteopagliazzi@gmail.com>
2020-06-29 10:52:23 +02:00
dependabot-preview[bot]
c6f825ac31 build(deps): bump @vue/cli-plugin-router in /website/client (#12349)
Bumps [@vue/cli-plugin-router](https://github.com/vuejs/vue-cli/tree/HEAD/packages/@vue/cli-plugin-router) from 4.4.4 to 4.4.6.
- [Release notes](https://github.com/vuejs/vue-cli/releases)
- [Changelog](https://github.com/vuejs/vue-cli/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/vuejs/vue-cli/commits/v4.4.6/packages/@vue/cli-plugin-router)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-06-29 10:46:53 +02:00
dependabot-preview[bot]
72acdf5884 build(deps): bump @vue/cli-service in /website/client (#12354)
Bumps [@vue/cli-service](https://github.com/vuejs/vue-cli/tree/HEAD/packages/@vue/cli-service) from 4.4.4 to 4.4.6.
- [Release notes](https://github.com/vuejs/vue-cli/releases)
- [Changelog](https://github.com/vuejs/vue-cli/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/vuejs/vue-cli/commits/v4.4.6/packages/@vue/cli-service)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-06-29 10:46:32 +02:00
dependabot-preview[bot]
8a527ec10a build(deps): bump sass from 1.26.8 to 1.26.9 in /website/client (#12355)
Bumps [sass](https://github.com/sass/dart-sass) from 1.26.8 to 1.26.9.
- [Release notes](https://github.com/sass/dart-sass/releases)
- [Changelog](https://github.com/sass/dart-sass/blob/master/CHANGELOG.md)
- [Commits](https://github.com/sass/dart-sass/compare/1.26.8...1.26.9)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-06-29 10:46:16 +02:00
dependabot-preview[bot]
d7d433175d build(deps): bump @vue/cli-plugin-babel in /website/client (#12344)
Bumps [@vue/cli-plugin-babel](https://github.com/vuejs/vue-cli/tree/HEAD/packages/@vue/cli-plugin-babel) from 4.4.4 to 4.4.6.
- [Release notes](https://github.com/vuejs/vue-cli/releases)
- [Changelog](https://github.com/vuejs/vue-cli/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/vuejs/vue-cli/commits/v4.4.6/packages/@vue/cli-plugin-babel)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-06-29 10:46:03 +02:00
dependabot-preview[bot]
0f75dd4dbe build(deps): bump superagent from 5.2.2 to 5.3.1 (#12352)
Bumps [superagent](https://github.com/visionmedia/superagent) from 5.2.2 to 5.3.1.
- [Release notes](https://github.com/visionmedia/superagent/releases)
- [Changelog](https://github.com/visionmedia/superagent/blob/master/HISTORY.md)
- [Commits](https://github.com/visionmedia/superagent/compare/v5.2.2...v5.3.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-06-29 10:45:38 +02:00
dependabot-preview[bot]
9098bde7ac build(deps): bump mongoose from 5.9.19 to 5.9.20 (#12343)
Bumps [mongoose](https://github.com/Automattic/mongoose) from 5.9.19 to 5.9.20.
- [Release notes](https://github.com/Automattic/mongoose/releases)
- [Changelog](https://github.com/Automattic/mongoose/blob/master/History.md)
- [Commits](https://github.com/Automattic/mongoose/compare/5.9.19...5.9.20)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-06-29 10:45:12 +02:00
dependabot-preview[bot]
a7a9747bf1 build(deps): bump winston from 3.3.0 to 3.3.3 (#12348)
Bumps [winston](https://github.com/winstonjs/winston) from 3.3.0 to 3.3.3.
- [Release notes](https://github.com/winstonjs/winston/releases)
- [Changelog](https://github.com/winstonjs/winston/blob/master/CHANGELOG.md)
- [Commits](https://github.com/winstonjs/winston/compare/v3.3.0...v3.3.3)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-06-29 10:44:50 +02:00
dependabot-preview[bot]
69e340ac59 build(deps): bump helmet from 3.23.1 to 3.23.3 (#12345)
Bumps [helmet](https://github.com/helmetjs/helmet) from 3.23.1 to 3.23.3.
- [Release notes](https://github.com/helmetjs/helmet/releases)
- [Changelog](https://github.com/helmetjs/helmet/blob/master/CHANGELOG.md)
- [Commits](https://github.com/helmetjs/helmet/compare/v3.23.1...v3.23.3)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-06-29 10:44:38 +02:00
dependabot-preview[bot]
c923bd8efb build(deps): bump uuid from 8.1.0 to 8.2.0 in /website/client (#12346)
Bumps [uuid](https://github.com/uuidjs/uuid) from 8.1.0 to 8.2.0.
- [Release notes](https://github.com/uuidjs/uuid/releases)
- [Changelog](https://github.com/uuidjs/uuid/blob/master/CHANGELOG.md)
- [Commits](https://github.com/uuidjs/uuid/compare/v8.1.0...v8.2.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-06-29 10:44:21 +02:00
dependabot-preview[bot]
4396713488 build(deps): bump uuid from 8.1.0 to 8.2.0 (#12350)
Bumps [uuid](https://github.com/uuidjs/uuid) from 8.1.0 to 8.2.0.
- [Release notes](https://github.com/uuidjs/uuid/releases)
- [Changelog](https://github.com/uuidjs/uuid/blob/master/CHANGELOG.md)
- [Commits](https://github.com/uuidjs/uuid/compare/v8.1.0...v8.2.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-06-29 10:43:54 +02:00
Matteo Pagliazzi
a352a351bf Merge https://github.com/HabitRPG/habitica/pull/12338 "Added the task type to the Delete alert window"
by @Shuyinsama
Squashed commit of the following:

commit 0c05ff510e4bd0d7161c49245dd11e6abe882d96
Author: Matteo Pagliazzi <matteopagliazzi@gmail.com>
Date:   Sat Jun 27 19:41:58 2020 +0200

    fix lint

commit 2e1e1e4071d57fa2853a4a8f481c94bd5836037f
Merge: cbc9af4ef5 fc46d73b1d
Author: Matteo Pagliazzi <matteopagliazzi@gmail.com>
Date:   Sat Jun 27 19:39:58 2020 +0200

    Merge branch 'feature/Deleting-a-reward-asks-you-if-you-want-to-delete-the-task' of https://github.com/Shuyinsama/habitica into Shuyinsama-feature/Deleting-a-reward-asks-you-if-you-want-to-delete-the-task

commit fc46d73b1d
Author: Patrick van Zadel <Shuyinsama@users.noreply.github.com>
Date:   Sat Jun 27 18:54:08 2020 +0200

    Renamed keys and fixed a check in the modal

commit ec5b765f5c
Author: Patrick van Zadel <Shuyinsama@users.noreply.github.com>
Date:   Sat Jun 27 18:45:25 2020 +0200

    Renamed translation keys

commit f0837dc571
Author: Patrick van Zadel <patrickvanzadel@eleven.nl>
Date:   Tue Jun 23 23:06:59 2020 +0200

    Added the task type to the "Delete" alert window so it now asks if you want to delete the task of type
    Edited the "Delete this ..." button in the edit task modal to represent the task type
2020-06-27 19:42:24 +02:00
Matteo Pagliazzi
cbc9af4ef5 fix(top menu): remove outline when sync button is pressed 2020-06-27 17:04:43 +02:00
jacobguinther
e42518f427 Make message links to deleted profiles display 404: (fixes 11020) (#12229)
* Make links to deleted profiles display 404

* Implement some requested changes

- remove comments
- remove 'await'
- rename error -> 404error
- remove catch block -> just check for 404

* Fix bug: Logged in user profile showing 404

- Initiallty logged in user will display fine
- This bug only appeared after you view a deleted users modal

* Remove 'user.active' attribute

- add change to data():
- user: undefined -> user: {}

* Shorten showMemberModal()

- Remove unused fetch profile data
- Remove unnecessary conditional

* Change user initial value to null

- Add userLoaded to data
2020-06-27 16:53:55 +02:00
Ieahleen
083417fe48 added trasnformation items to api cast spell description (#12340) 2020-06-27 16:17:48 +02:00
Sabe Jones
ea255579cb Merge branch 'release' into develop 2020-06-26 15:18:58 -05:00
negue
5e2bfc2c2f added tests: if collapseChecklist is allowed and all others are still blocked (#12337)
* tests: test if collapseChecklist is allowed and all others are still blocked

* fix lint
2020-06-26 19:05:41 +02:00
Matteo Pagliazzi
359ef47ce7 fix(get tasks): improve tests and add ability to fetch group tasks (#12339) 2020-06-26 16:50:49 +02:00
Matteo Pagliazzi
c7eae6f26b fix(push notifications): always strip markdown from push notification text 2020-06-26 15:27:02 +02:00
Sabe Jones
398b3c1f0d fix(ABtest): important parens 2020-06-25 15:06:58 -05:00
Melior
73844335de Merge branch 'origin/develop' into Weblate. 2020-06-25 21:33:42 +02:00
Melior
563744954e Translated using Weblate (Japanese)
Currently translated at 100.0% (249 of 249 strings)

Translation: Habitica/Subscriber
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/ja/

Translated using Weblate (Japanese)

Currently translated at 100.0% (28 of 28 strings)

Translation: Habitica/Rebirth
Translate-URL: https://translate.habitica.com/projects/habitica/rebirth/ja/

Translated using Weblate (Japanese)

Currently translated at 86.0% (1815 of 2109 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ja/

Translated using Weblate (Japanese)

Currently translated at 97.1% (682 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/ja/

Translated using Weblate (French)

Currently translated at 100.0% (702 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/fr/

Translated using Weblate (Spanish)

Currently translated at 96.4% (517 of 536 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/es/

Translated using Weblate (Japanese)

Currently translated at 100.0% (211 of 211 strings)

Translation: Habitica/Settings
Translate-URL: https://translate.habitica.com/projects/habitica/settings/ja/

Translated using Weblate (Russian)

Currently translated at 96.9% (2045 of 2109 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ru/

Translated using Weblate (Dutch)

Currently translated at 97.1% (2048 of 2109 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/nl/

Translated using Weblate (Japanese)

Currently translated at 97.1% (682 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/ja/

Translated using Weblate (Spanish)

Currently translated at 95.8% (514 of 536 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/es/

Translated using Weblate (Spanish)

Currently translated at 100.0% (213 of 213 strings)

Translation: Habitica/Tasks
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/es/

Translated using Weblate (Spanish)

Currently translated at 88.7% (221 of 249 strings)

Translation: Habitica/Subscriber
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/es/

Translated using Weblate (Spanish)

Currently translated at 100.0% (28 of 28 strings)

Translation: Habitica/Rebirth
Translate-URL: https://translate.habitica.com/projects/habitica/rebirth/es/

Translated using Weblate (Spanish)

Currently translated at 100.0% (211 of 211 strings)

Translation: Habitica/Settings
Translate-URL: https://translate.habitica.com/projects/habitica/settings/es/

Translated using Weblate (Spanish)

Currently translated at 100.0% (140 of 140 strings)

Translation: Habitica/Quests
Translate-URL: https://translate.habitica.com/projects/habitica/quests/es/

Translated using Weblate (Spanish)

Currently translated at 100.0% (143 of 143 strings)

Translation: Habitica/Pets
Translate-URL: https://translate.habitica.com/projects/habitica/pets/es/

Translated using Weblate (Spanish)

Currently translated at 100.0% (173 of 173 strings)

Translation: Habitica/Npc
Translate-URL: https://translate.habitica.com/projects/habitica/npc/es/

Translated using Weblate (Spanish)

Currently translated at 100.0% (8 of 8 strings)

Translation: Habitica/Overview
Translate-URL: https://translate.habitica.com/projects/habitica/overview/es/

Translated using Weblate (Spanish)

Currently translated at 89.6% (1890 of 2109 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/es/

Translated using Weblate (Spanish)

Currently translated at 94.5% (507 of 536 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/es/

Translated using Weblate (Latin)

Currently translated at 83.9% (450 of 536 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/la/

Translated using Weblate (Latin)

Currently translated at 86.7% (72 of 83 strings)

Translation: Habitica/Achievements
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/la/

Translated using Weblate (Russian)

Currently translated at 97.5% (81 of 83 strings)

Translation: Habitica/Achievements
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/ru/

Translated using Weblate (Japanese)

Currently translated at 100.0% (213 of 213 strings)

Translation: Habitica/Tasks
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/ja/

Translated using Weblate (Japanese)

Currently translated at 99.5% (248 of 249 strings)

Translation: Habitica/Subscriber
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/ja/

Translated using Weblate (German)

Currently translated at 100.0% (28 of 28 strings)

Translation: Habitica/Rebirth
Translate-URL: https://translate.habitica.com/projects/habitica/rebirth/de/

Translated using Weblate (Japanese)

Currently translated at 100.0% (173 of 173 strings)

Translation: Habitica/Npc
Translate-URL: https://translate.habitica.com/projects/habitica/npc/ja/

Translated using Weblate (Russian)

Currently translated at 100.0% (8 of 8 strings)

Translation: Habitica/Overview
Translate-URL: https://translate.habitica.com/projects/habitica/overview/ru/

Translated using Weblate (Japanese)

Currently translated at 100.0% (8 of 8 strings)

Translation: Habitica/Overview
Translate-URL: https://translate.habitica.com/projects/habitica/overview/ja/

Translated using Weblate (Russian)

Currently translated at 98.3% (182 of 185 strings)

Translation: Habitica/Limited
Translate-URL: https://translate.habitica.com/projects/habitica/limited/ru/

Translated using Weblate (Russian)

Currently translated at 96.9% (2045 of 2109 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ru/

Translated using Weblate (Russian)

Currently translated at 98.2% (55 of 56 strings)

Translation: Habitica/Faq
Translate-URL: https://translate.habitica.com/projects/habitica/faq/ru/

Translated using Weblate (Japanese)

Currently translated at 100.0% (56 of 56 strings)

Translation: Habitica/Faq
Translate-URL: https://translate.habitica.com/projects/habitica/faq/ja/

Translated using Weblate (Chinese (Simplified))

Currently translated at 99.8% (701 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/zh_Hans/

Translated using Weblate (Russian)

Currently translated at 99.2% (697 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/ru/

Translated using Weblate (German)

Currently translated at 99.7% (700 of 702 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/de/

Translated using Weblate (German)

Currently translated at 100.0% (140 of 140 strings)

Translation: Habitica/Quests
Translate-URL: https://translate.habitica.com/projects/habitica/quests/de/

Translated using Weblate (Russian)

Currently translated at 100.0% (8 of 8 strings)

Translation: Habitica/Overview
Translate-URL: https://translate.habitica.com/projects/habitica/overview/ru/

Translated using Weblate (German)

Currently translated at 100.0% (8 of 8 strings)

Translation: Habitica/Overview
Translate-URL: https://translate.habitica.com/projects/habitica/overview/de/

Translated using Weblate (Russian)

Currently translated at 97.2% (180 of 185 strings)

Translation: Habitica/Limited
Translate-URL: https://translate.habitica.com/projects/habitica/limited/ru/

Translated using Weblate (German)

Currently translated at 99.4% (184 of 185 strings)

Translation: Habitica/Limited
Translate-URL: https://translate.habitica.com/projects/habitica/limited/de/

Translated using Weblate (Russian)

Currently translated at 96.3% (2031 of 2109 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ru/

Translated using Weblate (German)

Currently translated at 99.2% (2093 of 2109 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/de/

Translated using Weblate (Russian)

Currently translated at 85.7% (48 of 56 strings)

Translation: Habitica/Faq
Translate-URL: https://translate.habitica.com/projects/habitica/faq/ru/

Translated using Weblate (Dutch)

Currently translated at 83.9% (47 of 56 strings)

Translation: Habitica/Faq
Translate-URL: https://translate.habitica.com/projects/habitica/faq/nl/

Translated using Weblate (German)

Currently translated at 100.0% (56 of 56 strings)

Translation: Habitica/Faq
Translate-URL: https://translate.habitica.com/projects/habitica/faq/de/

Translated using Weblate (French)

Currently translated at 100.0% (2109 of 2109 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/fr/

Translated using Weblate (German)

Currently translated at 98.7% (2082 of 2109 strings)

Translation: Habitica/Gear
Translate-URL: https://translate.habitica.com/projects/habitica/gear/de/

Translated using Weblate (German)

Currently translated at 100.0% (360 of 360 strings)

Translation: Habitica/Content
Translate-URL: https://translate.habitica.com/projects/habitica/content/de/
2020-06-25 21:33:25 +02:00
1189 changed files with 53858 additions and 45327 deletions

View File

@@ -22,6 +22,7 @@ jobs:
npm ci npm ci
env: env:
CI: true CI: true
NODE_ENV: test
- run: npm run lint-no-fix - run: npm run lint-no-fix
apidoc: apidoc:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -42,6 +43,7 @@ jobs:
npm ci npm ci
env: env:
CI: true CI: true
NODE_ENV: test
- run: npm run apidoc - run: npm run apidoc
sanity: sanity:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -62,6 +64,7 @@ jobs:
npm ci npm ci
env: env:
CI: true CI: true
NODE_ENV: test
- run: npm run test:sanity - run: npm run test:sanity
common: common:
@@ -83,6 +86,7 @@ jobs:
npm ci npm ci
env: env:
CI: true CI: true
NODE_ENV: test
- run: npm run test:common - run: npm run test:common
content: content:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -103,6 +107,7 @@ jobs:
npm ci npm ci
env: env:
CI: true CI: true
NODE_ENV: test
- run: npm run test:content - run: npm run test:content
api-unit: api-unit:
@@ -110,6 +115,7 @@ jobs:
strategy: strategy:
matrix: matrix:
node-version: [12.x] node-version: [12.x]
mongodb-version: [4.2]
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v1
with: with:
@@ -118,13 +124,18 @@ jobs:
uses: actions/setup-node@v1 uses: actions/setup-node@v1
with: with:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}
- run: sudo docker run --name mongo -d -p 27017:27017 mongo:4.2 - name: Start MongoDB ${{ matrix.mongodb-version }} Replica Set
uses: supercharge/mongodb-github-action@1.3.0
with:
mongodb-version: ${{ matrix.mongodb-version }}
mongodb-replica-set: rs
- run: cp config.json.example config.json - run: cp config.json.example config.json
- name: npm install - name: npm install
run: | run: |
npm ci npm ci
env: env:
CI: true CI: true
NODE_ENV: test
- run: npm run test:api:unit - run: npm run test:api:unit
env: env:
REQUIRES_SERVER=true: true REQUIRES_SERVER=true: true
@@ -133,6 +144,7 @@ jobs:
strategy: strategy:
matrix: matrix:
node-version: [12.x] node-version: [12.x]
mongodb-version: [4.2]
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v1
with: with:
@@ -141,13 +153,18 @@ jobs:
uses: actions/setup-node@v1 uses: actions/setup-node@v1
with: with:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}
- run: sudo docker run --name mongo -d -p 27017:27017 mongo:4.2 - name: Start MongoDB ${{ matrix.mongodb-version }} Replica Set
uses: supercharge/mongodb-github-action@1.3.0
with:
mongodb-version: ${{ matrix.mongodb-version }}
mongodb-replica-set: rs
- run: cp config.json.example config.json - run: cp config.json.example config.json
- name: npm install - name: npm install
run: | run: |
npm ci npm ci
env: env:
CI: true CI: true
NODE_ENV: test
- run: npm run test:api-v3:integration - run: npm run test:api-v3:integration
env: env:
REQUIRES_SERVER=true: true REQUIRES_SERVER=true: true
@@ -156,6 +173,7 @@ jobs:
strategy: strategy:
matrix: matrix:
node-version: [12.x] node-version: [12.x]
mongodb-version: [4.2]
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v1
with: with:
@@ -164,13 +182,18 @@ jobs:
uses: actions/setup-node@v1 uses: actions/setup-node@v1
with: with:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}
- run: sudo docker run --name mongo -d -p 27017:27017 mongo:4.2 - name: Start MongoDB ${{ matrix.mongodb-version }} Replica Set
uses: supercharge/mongodb-github-action@1.3.0
with:
mongodb-version: ${{ matrix.mongodb-version }}
mongodb-replica-set: rs
- run: cp config.json.example config.json - run: cp config.json.example config.json
- name: npm install - name: npm install
run: | run: |
npm ci npm ci
env: env:
CI: true CI: true
NODE_ENV: test
- run: npm run test:api-v4:integration - run: npm run test:api-v4:integration
env: env:
REQUIRES_SERVER=true: true REQUIRES_SERVER=true: true
@@ -194,5 +217,6 @@ jobs:
npm ci npm ci
env: env:
CI: true CI: true
NODE_ENV: test
- run: npm run test:unit - run: npm run test:unit
working-directory: ./website/client working-directory: ./website/client

5
.gitignore vendored
View File

@@ -38,7 +38,12 @@ yarn.lock
.elasticbeanstalk/* .elasticbeanstalk/*
!.elasticbeanstalk/*.cfg.yml !.elasticbeanstalk/*.cfg.yml
!.elasticbeanstalk/*.global.yml !.elasticbeanstalk/*.global.yml
/.vscode /.vscode
# webstorm fake webpack for path intellisense # webstorm fake webpack for path intellisense
webpack.webstorm.config webpack.webstorm.config
# mongodb replica set for local dev
mongodb-*.tgz
/mongodb-data

View File

@@ -2,6 +2,10 @@
"name": "Habitica V3 API Documentation", "name": "Habitica V3 API Documentation",
"title": "Habitica", "title": "Habitica",
"url": "https://habitica.com", "url": "https://habitica.com",
"header": {
"title": "Introduction",
"filename": "apidoc/header.md"
},
"template": { "template": {
"withCompare": false "withCompare": false
} }

5
apidoc/header.md Normal file
View File

@@ -0,0 +1,5 @@
# Introduction
This webpage includes the documentation for version 3 of the [Habitica](https://habitica.com) API.
If you're developing a 3rd party tool that uses the Habitica API you should read the [Guidance for Comrades](https://habitica.fandom.com/wiki/Guidance_for_Comrades) and in particular the section called [Rules for Third-Party Tools](https://habitica.fandom.com/wiki/Guidance_for_Comrades#Rules_for_Third-Party_Tools) which includes suggestions on how to best use the API and the rules to follow when interacting with it.

View File

@@ -32,7 +32,8 @@
"LOGGLY_SUBDOMAIN": "example-subdomain", "LOGGLY_SUBDOMAIN": "example-subdomain",
"LOGGLY_TOKEN": "example-token", "LOGGLY_TOKEN": "example-token",
"MAINTENANCE_MODE": "false", "MAINTENANCE_MODE": "false",
"NODE_DB_URI": "mongodb://localhost:27017/habitrpg", "NODE_DB_URI": "mongodb://localhost:27017/habitica-dev?replicaSet=rs",
"TEST_DB_URI": "mongodb://localhost:27017/habitica-test?replicaSet=rs",
"MONGODB_POOL_SIZE": "10", "MONGODB_POOL_SIZE": "10",
"NODE_ENV": "development", "NODE_ENV": "development",
"PATH": "bin:node_modules/.bin:/usr/local/bin:/usr/bin:/bin", "PATH": "bin:node_modules/.bin:/usr/local/bin:/usr/bin:/bin",
@@ -70,7 +71,6 @@
"SLACK_URL": "https://hooks.slack.com/services/some-url", "SLACK_URL": "https://hooks.slack.com/services/some-url",
"STRIPE_API_KEY": "aaaabbbbccccddddeeeeffff00001111", "STRIPE_API_KEY": "aaaabbbbccccddddeeeeffff00001111",
"STRIPE_PUB_KEY": "22223333444455556666777788889999", "STRIPE_PUB_KEY": "22223333444455556666777788889999",
"TEST_DB_URI": "mongodb://localhost:27017/habitrpg_test",
"TRANSIFEX_SLACK_CHANNEL": "transifex", "TRANSIFEX_SLACK_CHANNEL": "transifex",
"WEB_CONCURRENCY": 1, "WEB_CONCURRENCY": 1,
"SKIP_SSL_CHECK_KEY": "key", "SKIP_SSL_CHECK_KEY": "key",
@@ -80,5 +80,9 @@
"APPLE_AUTH_CLIENT_ID": "", "APPLE_AUTH_CLIENT_ID": "",
"APPLE_AUTH_KEY_ID": "", "APPLE_AUTH_KEY_ID": "",
"BLOCKED_IPS": "", "BLOCKED_IPS": "",
"LOG_AMPLITUDE_EVENTS": "false" "LOG_AMPLITUDE_EVENTS": "false",
"RATE_LIMITER_ENABLED": "false",
"REDIS_HOST": "aaabbbcccdddeeefff",
"REDIS_PORT": "1234",
"REDIS_PASSWORD": "12345678"
} }

View File

@@ -1,5 +1,12 @@
/* eslint-disable no-console */
import gulp from 'gulp'; import gulp from 'gulp';
import path from 'path';
import babel from 'gulp-babel'; import babel from 'gulp-babel';
import os from 'os';
import fs from 'fs';
import spawn from 'cross-spawn'; // eslint-disable-line import/no-extraneous-dependencies
import clean from 'rimraf';
gulp.task('build:babel:server', () => gulp.src('website/server/**/*.js') gulp.task('build:babel:server', () => gulp.src('website/server/**/*.js')
.pipe(babel()) .pipe(babel())
@@ -24,10 +31,67 @@ gulp.task('build:prod', gulp.series(
done => done(), done => done(),
)); ));
// Due to this issue https://github.com/vkarpov15/run-rs/issues/45
// When used on windows `run-rs` must first be run without the `--keep` option
// in order to be setup correctly, afterwards it can be used.
const MONGO_PATH = path.join(__dirname, '/../mongodb-data/');
gulp.task('build:prepare-mongo', async () => {
if (fs.existsSync(MONGO_PATH)) {
// console.log('MongoDB data folder exists, skipping setup.');
return;
}
if (os.platform() !== 'win32') {
// console.log('Not on Windows, skipping MongoDB setup.');
return;
}
console.log('MongoDB data folder is missing, setting up.'); // eslint-disable-line no-console
// use run-rs without --keep, kill it as soon as the replica set starts
const runRsProcess = spawn('run-rs', ['-v', '4.2.8', '-l', 'ubuntu1804', '--dbpath', 'mongodb-data', '--number', '1', '--quiet']);
for await (const chunk of runRsProcess.stdout) {
const stringChunk = chunk.toString();
console.log(stringChunk); // eslint-disable-line no-console
// kills the process after the replica set is setup
if (stringChunk.includes('Started replica set')) {
console.log('MongoDB setup correctly.'); // eslint-disable-line no-console
runRsProcess.kill();
}
}
let error = '';
for await (const chunk of runRsProcess.stderr) {
const stringChunk = chunk.toString();
error += stringChunk;
}
const exitCode = await new Promise(resolve => {
runRsProcess.on('close', resolve);
});
if (exitCode || error.length > 0) {
// remove any leftover files
clean.sync(MONGO_PATH);
throw new Error(`Error running run-rs: ${error}`);
}
});
gulp.task('build:dev', gulp.series(
'build:prepare-mongo',
done => done(),
));
const buildArgs = []; const buildArgs = [];
if (process.env.NODE_ENV === 'production') { // eslint-disable-line no-process-env if (process.env.NODE_ENV === 'production') { // eslint-disable-line no-process-env
buildArgs.push('build:prod'); buildArgs.push('build:prod');
} else if (process.env.NODE_ENV !== 'test') { // eslint-disable-line no-process-env
buildArgs.push('build:dev');
} }
gulp.task('build', gulp.series(buildArgs, done => { gulp.task('build', gulp.series(buildArgs, done => {

View File

@@ -3,6 +3,10 @@ import nconf from 'nconf';
import repl from 'repl'; import repl from 'repl';
import gulp from 'gulp'; import gulp from 'gulp';
import logger from '../website/server/libs/logger'; import logger from '../website/server/libs/logger';
import {
getDevelopmentConnectionUrl,
getDefaultConnectionOptions,
} from '../website/server/libs/mongodb';
// Add additional properties to the repl's context // Add additional properties to the repl's context
const improveRepl = context => { const improveRepl = context => {
@@ -26,13 +30,14 @@ const improveRepl = context => {
context.Group = require('../website/server/models/group').model; // eslint-disable-line global-require context.Group = require('../website/server/models/group').model; // eslint-disable-line global-require
context.User = require('../website/server/models/user').model; // eslint-disable-line global-require context.User = require('../website/server/models/user').model; // eslint-disable-line global-require
const isProd = nconf.get('NODE_ENV') === 'production'; const IS_PROD = nconf.get('NODE_ENV') === 'production';
const mongooseOptions = !isProd ? {} : { const NODE_DB_URI = nconf.get('NODE_DB_URI');
keepAlive: 1,
connectTimeoutMS: 30000, const mongooseOptions = getDefaultConnectionOptions();
}; const connectionUrl = IS_PROD ? NODE_DB_URI : getDevelopmentConnectionUrl(NODE_DB_URI);
mongoose.connect( mongoose.connect(
nconf.get('NODE_DB_URI'), connectionUrl,
mongooseOptions, mongooseOptions,
err => { err => {
if (err) throw err; if (err) throw err;

View File

@@ -6,6 +6,10 @@ import nconf from 'nconf';
import { import {
pipe, pipe,
} from './taskHelper'; } from './taskHelper';
import {
getDevelopmentConnectionUrl,
getDefaultConnectionOptions,
} from '../website/server/libs/mongodb';
// TODO rewrite // TODO rewrite
@@ -44,7 +48,10 @@ gulp.task('test:nodemon', gulp.series(done => {
}, 'nodemon')); }, 'nodemon'));
gulp.task('test:prepare:mongo', cb => { gulp.task('test:prepare:mongo', cb => {
mongoose.connect(TEST_DB_URI, err => { const mongooseOptions = getDefaultConnectionOptions();
const connectionUrl = getDevelopmentConnectionUrl(TEST_DB_URI);
mongoose.connect(connectionUrl, mongooseOptions, err => {
if (err) return cb(`Unable to connect to mongo database. Are you sure it's running? \n\n${err}`); if (err) return cb(`Unable to connect to mongo database. Are you sure it's running? \n\n${err}`);
return mongoose.connection.dropDatabase(err2 => { return mongoose.connection.dropDatabase(err2 => {
if (err2) return cb(err2); if (err2) return cb(err2);
@@ -176,7 +183,7 @@ gulp.task('test:api:unit:run', done => {
gulp.task('test:api:unit:watch', () => gulp.watch(['website/server/libs/*', 'test/api/unit/**/*', 'website/server/controllers/**/*'], gulp.series('test:api:unit:run', done => done()))); gulp.task('test:api:unit:watch', () => gulp.watch(['website/server/libs/*', 'test/api/unit/**/*', 'website/server/controllers/**/*'], gulp.series('test:api:unit:run', done => done())));
gulp.task('test:api-v3:integration', done => { gulp.task('test:api-v3:integration', gulp.series('test:prepare:mongo', done => {
const runner = exec( const runner = exec(
testBin('istanbul cover --dir coverage/api-v3-integration --report lcovonly node_modules/mocha/bin/_mocha -- test/api/v3/integration --recursive --require ./test/helpers/start-server'), testBin('istanbul cover --dir coverage/api-v3-integration --report lcovonly node_modules/mocha/bin/_mocha -- test/api/v3/integration --recursive --require ./test/helpers/start-server'),
{ maxBuffer: 500 * 1024 }, { maxBuffer: 500 * 1024 },
@@ -189,7 +196,7 @@ gulp.task('test:api-v3:integration', done => {
); );
pipe(runner); pipe(runner);
}); }));
gulp.task('test:api-v3:integration:watch', () => gulp.watch([ gulp.task('test:api-v3:integration:watch', () => gulp.watch([
'website/server/controllers/api-v3/**/*', 'common/script/ops/*', 'website/server/libs/*.js', 'website/server/controllers/api-v3/**/*', 'common/script/ops/*', 'website/server/libs/*.js',
@@ -206,7 +213,7 @@ gulp.task('test:api-v3:integration:separate-server', done => {
pipe(runner); pipe(runner);
}); });
gulp.task('test:api-v4:integration', done => { gulp.task('test:api-v4:integration', gulp.series('test:prepare:mongo', done => {
const runner = exec( const runner = exec(
testBin('istanbul cover --dir coverage/api-v4-integration --report lcovonly node_modules/mocha/bin/_mocha -- test/api/v4 --recursive --require ./test/helpers/start-server'), testBin('istanbul cover --dir coverage/api-v4-integration --report lcovonly node_modules/mocha/bin/_mocha -- test/api/v4 --recursive --require ./test/helpers/start-server'),
{ maxBuffer: 500 * 1024 }, { maxBuffer: 500 * 1024 },
@@ -219,7 +226,7 @@ gulp.task('test:api-v4:integration', done => {
); );
pipe(runner); pipe(runner);
}); }));
gulp.task('test:api-v4:integration:separate-server', done => { gulp.task('test:api-v4:integration:separate-server', done => {
const runner = exec( const runner = exec(
@@ -231,11 +238,16 @@ gulp.task('test:api-v4:integration:separate-server', done => {
pipe(runner); pipe(runner);
}); });
gulp.task('test:api:unit', gulp.series(
'test:prepare:mongo',
'test:api:unit:run',
done => done(),
));
gulp.task('test', gulp.series( gulp.task('test', gulp.series(
'test:sanity', 'test:sanity',
'test:content', 'test:content',
'test:common', 'test:common',
'test:prepare:mongo',
'test:api:unit:run', 'test:api:unit:run',
'test:api-v3:integration', 'test:api-v3:integration',
'test:api-v4:integration', 'test:api-v4:integration',
@@ -243,14 +255,7 @@ gulp.task('test', gulp.series(
)); ));
gulp.task('test:api-v3', gulp.series( gulp.task('test:api-v3', gulp.series(
'test:prepare:mongo', 'test:api:unit',
'test:api:unit:run',
'test:api-v3:integration', 'test:api-v3:integration',
done => done(), done => done(),
)); ));
gulp.task('test:api:unit', gulp.series(
'test:prepare:mongo',
'test:api:unit:run',
done => done(),
));

View File

@@ -12,7 +12,6 @@ const SLACK_CONFIG = {
const LOCALES = './website/common/locales/'; const LOCALES = './website/common/locales/';
const ENGLISH_LOCALE = `${LOCALES}en/`; const ENGLISH_LOCALE = `${LOCALES}en/`;
function getArrayOfLanguages () { function getArrayOfLanguages () {
const languages = fs.readdirSync(LOCALES); const languages = fs.readdirSync(LOCALES);
languages.shift(); // Remove README.md from array of languages languages.shift(); // Remove README.md from array of languages

View File

@@ -0,0 +1,59 @@
/* eslint-disable no-console */
const MIGRATION_NAME = '20200721_summer_splash_orcas';
import { model as User } from '../../../website/server/models/user';
const progressCount = 1000;
let count = 0;
async function updateUser (user) {
count++;
let set;
if (user && user.items && user.items.pets && typeof user.items.pets['Orca-Base'] !== 'undefined') {
set = { migration: MIGRATION_NAME };
} else if (user && user.items && user.items.mounts && typeof user.items.mounts['Orca-Base'] !== 'undefined') {
set = { migration: MIGRATION_NAME, 'items.pets.Orca-Base': 5 };
} else {
set = { migration: MIGRATION_NAME, 'items.mounts.Orca-Base': true };
}
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
return await User.update({ _id: user._id }, { $set: set }).exec();
}
export default async function processUsers () {
let query = {
migration: {$ne: MIGRATION_NAME},
'auth.timestamps.loggedin': {$gt: new Date('2020-06-21')},
};
const fields = {
_id: 1,
items: 1,
};
while (true) { // eslint-disable-line no-constant-condition
const users = await User // eslint-disable-line no-await-in-loop
.find(query)
.limit(250)
.sort({_id: 1})
.select(fields)
.lean()
.exec();
if (users.length === 0) {
console.warn('All appropriate users found and modified.');
console.warn(`\n${count} users processed\n`);
break;
} else {
query._id = {
$gt: users[users.length - 1],
};
}
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
}
};

View File

@@ -0,0 +1,87 @@
/* eslint-disable no-console */
const MIGRATION_NAME = '20200731_naming_day';
import { v4 as uuid } from 'uuid';
import { model as User } from '../../../website/server/models/user';
const progressCount = 1000;
let count = 0;
async function updateUser (user) {
count++;
let set;
let push;
const inc = {
'items.food.Cake_Base': 1,
'items.food.Cake_CottonCandyBlue': 1,
'items.food.Cake_CottonCandyPink': 1,
'items.food.Cake_Desert': 1,
'items.food.Cake_Golden': 1,
'items.food.Cake_Red': 1,
'items.food.Cake_Shade': 1,
'items.food.Cake_Skeleton': 1,
'items.food.Cake_White': 1,
'items.food.Cake_Zombie': 1,
'achievements.habiticaDays': 1,
};
if (user && user.items && user.items.gear && user.items.gear.owned && typeof user.items.gear.owned.back_special_namingDay2020 !== 'undefined') {
set = { migration: MIGRATION_NAME };
} else if (user && user.items && user.items.gear && user.items.gear.owned && typeof user.items.gear.owned.body_special_namingDay2018 !== 'undefined') {
set = { migration: MIGRATION_NAME, 'items.gear.owned.back_special_namingDay2020': false };
push = { pinnedItems: { type: 'marketGear', path: 'gear.flat.back_special_namingDay2020', _id: uuid() }};
} else if (user && user.items && user.items.gear && user.items.gear.owned && typeof user.items.gear.owned.head_special_namingDay2017 !== 'undefined') {
set = { migration: MIGRATION_NAME, 'items.gear.owned.body_special_namingDay2018': false };
push = { pinnedItems: { type: 'marketGear', path: 'gear.flat.body_special_namingDay2018', _id: uuid() }};
} else if (user && user.items && user.items.pets && typeof user.items.pets['Gryphon-RoyalPurple'] !== 'undefined') {
set = { migration: MIGRATION_NAME, 'items.gear.owned.head_special_namingDay2017': false };
push = { pinnedItems: { type: 'marketGear', path: 'gear.flat.head_special_namingDay2017', _id: uuid() }};
} else if (user && user.items && user.items.mounts && typeof user.items.mounts['Gryphon-RoyalPurple'] !== 'undefined') {
set = { migration: MIGRATION_NAME, 'items.pets.Gryphon-RoyalPurple': 5 };
} else {
set = { migration: MIGRATION_NAME, 'items.mounts.Gryphon-RoyalPurple': true };
}
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
if (push) {
return await User.update({ _id: user._id }, { $set: set, $inc: inc, $push: push }).exec();
} else {
return await User.update({ _id: user._id }, { $set: set, $inc: inc }).exec();
}
}
export default async function processUsers () {
let query = {
migration: { $ne: MIGRATION_NAME },
'auth.timestamps.loggedin': { $gt: new Date('2020-07-01') },
};
const fields = {
_id: 1,
items: 1,
};
while (true) { // eslint-disable-line no-constant-condition
const users = await User // eslint-disable-line no-await-in-loop
.find(query)
.limit(250)
.sort({_id: 1})
.select(fields)
.lean()
.exec();
if (users.length === 0) {
console.warn('All appropriate users found and modified.');
console.warn(`\n${count} users processed\n`);
break;
} else {
query._id = {
$gt: users[users.length - 1]._id,
};
}
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
}
};

View File

@@ -1,5 +1,5 @@
/* eslint-disable no-console */ /* eslint-disable no-console */
const MIGRATION_NAME = '20200218_pet_color_achievements'; const MIGRATION_NAME = '20200818_pet_color_achievements';
import { model as User } from '../../../website/server/models/user'; import { model as User } from '../../../website/server/models/user';
const progressCount = 1000; const progressCount = 1000;
@@ -14,31 +14,31 @@ async function updateUser (user) {
if (user && user.items && user.items.pets) { if (user && user.items && user.items.pets) {
const pets = user.items.pets; const pets = user.items.pets;
if (pets['Wolf-CottonCandyPink'] > 0 if (pets['Wolf-Golden'] > 0
&& pets['TigerCub-CottonCandyPink'] > 0 && pets['TigerCub-Golden'] > 0
&& pets['PandaCub-CottonCandyPink'] > 0 && pets['PandaCub-Golden'] > 0
&& pets['LionCub-CottonCandyPink'] > 0 && pets['LionCub-Golden'] > 0
&& pets['Fox-CottonCandyPink'] > 0 && pets['Fox-Golden'] > 0
&& pets['FlyingPig-CottonCandyPink'] > 0 && pets['FlyingPig-Golden'] > 0
&& pets['Dragon-CottonCandyPink'] > 0 && pets['Dragon-Golden'] > 0
&& pets['Cactus-CottonCandyPink'] > 0 && pets['Cactus-Golden'] > 0
&& pets['BearCub-CottonCandyPink'] > 0) { && pets['BearCub-Golden'] > 0) {
set['achievements.tickledPink'] = true; set['achievements.goodAsGold'] = true;
} }
} }
if (user && user.items && user.items.mounts) { if (user && user.items && user.items.mounts) {
const mounts = user.items.mounts; const mounts = user.items.mounts;
if (mounts['Wolf-CottonCandyPink'] if (mounts['Wolf-Golden']
&& mounts['TigerCub-CottonCandyPink'] && mounts['TigerCub-Golden']
&& mounts['PandaCub-CottonCandyPink'] && mounts['PandaCub-Golden']
&& mounts['LionCub-CottonCandyPink'] && mounts['LionCub-Golden']
&& mounts['Fox-CottonCandyPink'] && mounts['Fox-Golden']
&& mounts['FlyingPig-CottonCandyPink'] && mounts['FlyingPig-Golden']
&& mounts['Dragon-CottonCandyPink'] && mounts['Dragon-Golden']
&& mounts['Cactus-CottonCandyPink'] && mounts['Cactus-Golden']
&& mounts['BearCub-CottonCandyPink'] ) { && mounts['BearCub-Golden'] ) {
set['achievements.rosyOutlook'] = true; set['achievements.allThatGlitters'] = true;
} }
} }
@@ -50,7 +50,7 @@ async function updateUser (user) {
module.exports = async function processUsers () { module.exports = async function processUsers () {
let query = { let query = {
migration: { $ne: MIGRATION_NAME }, migration: { $ne: MIGRATION_NAME },
'auth.timestamps.loggedin': { $gt: new Date('2020-02-01') }, 'auth.timestamps.loggedin': { $gt: new Date('2020-08-01') },
}; };
const fields = { const fields = {

View File

@@ -32,13 +32,11 @@
* *
*/ */
// CONFIGURATION: // CONFIGURATION:
// - Change the uuid below to be the user's uuid. // - Change the uuid below to be the user's uuid.
// - Change ALL instances of "todos" to "habits"/"dailys"/"rewards" as // - Change ALL instances of "todos" to "habits"/"dailys"/"rewards" as
// needed. Do not miss any of them! // needed. Do not miss any of them!
const uuid = '30fb2640-7121-4968-ace5-f385e60ea6c5'; const uuid = '30fb2640-7121-4968-ace5-f385e60ea6c5';
db.users.aggregate([ db.users.aggregate([

View File

@@ -2,7 +2,6 @@
// @authorName = 'TheHollidayInn'; // in case script author needs to know when their ... // @authorName = 'TheHollidayInn'; // in case script author needs to know when their ...
// @authorUuid = ''; // ... own data is done // @authorUuid = ''; // ... own data is done
/* /*
* This migration moves chat off of groups and into their own model * This migration moves chat off of groups and into their own model
*/ */
@@ -38,7 +37,6 @@ async function moveGroupChatToModel (skip = 0) {
return chatpromises; return chatpromises;
}); });
const reducedPromises = promises.reduce((acc, curr) => { const reducedPromises = promises.reduce((acc, curr) => {
acc = acc.concat(curr); // eslint-disable-line no-param-reassign acc = acc.concat(curr); // eslint-disable-line no-param-reassign
return acc; return acc;

View File

@@ -25,7 +25,6 @@ const monk = require('monk'); // eslint-disable-line import/no-extraneous-depend
const dbUsers = monk(connectionString).get('users', { castIds: false }); const dbUsers = monk(connectionString).get('users', { castIds: false });
function processUsers (lastId) { function processUsers (lastId) {
// specify a query to limit the affected users (empty for all users): // specify a query to limit the affected users (empty for all users):
const query = { const query = {
@@ -86,7 +85,6 @@ function updateUser (user) {
const set = { migration: migrationName, 'flags.armoireEmpty': false }; const set = { migration: migrationName, 'flags.armoireEmpty': false };
if (user.flags.armoireEmpty) { if (user.flags.armoireEmpty) {
// this user believes their armoire has no more items in it // this user believes their armoire has no more items in it
if ( if (

View File

@@ -121,5 +121,4 @@ function exiting (code, msg) {
process.exit(code); process.exit(code);
} }
processUsers(); processUsers();

View File

@@ -9,7 +9,6 @@ const authorUuid = 'ed4c688c-6652-4a92-9d03-a5a79844174a'; // ... own data is do
*/ */
const connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE const connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
const dbUsers = monk(connectionString).get('users', { castIds: false }); const dbUsers = monk(connectionString).get('users', { castIds: false });
function processUsers (lastId) { function processUsers (lastId) {

View File

@@ -0,0 +1,73 @@
import { model as User } from '../../website/server/models/user';
const MIGRATION_NAME = 'tag-challenge-field-string2bool';
const progressCount = 1000;
let count = 0;
export default async function processUsers () {
const query = {
migration: { $ne: MIGRATION_NAME },
tags: {
$elemMatch: {
challenge: {
$exists: true,
$type: 'string',
},
},
},
};
while (true) { // eslint-disable-line no-constant-condition
// eslint-disable-next-line no-await-in-loop
const users = await User.find(query)
.sort({ _id: 1 })
.limit(250)
.select({ _id: 1, tags: 1 })
.lean()
.exec();
if (users.length === 0) {
console.warn('All appropriate users found and modified.');
console.warn(`\n${count} users processed\n`);
break;
} else {
query._id = {
$gt: users[users.length - 1],
};
}
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
}
}
async function updateUser (user) {
count += 1;
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
let requiresUpdate = false;
if (user && user.tags) {
user.tags.forEach(tag => {
if (tag && typeof tag.challenge === 'string') {
requiresUpdate = true;
if (tag.challenge === 'true') {
tag.challenge = true;
} else if (tag.challenge === 'false') {
tag.challenge = false;
} else {
tag.challenge = null;
}
}
});
}
if (requiresUpdate) {
const set = {
migration: MIGRATION_NAME,
tags: user.tags,
};
return User.update({ _id: user._id }, { $set: set }).exec();
}
return null;
}

8144
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,18 +1,18 @@
{ {
"name": "habitica", "name": "habitica",
"description": "A habit tracker app which treats your goals like a Role Playing Game.", "description": "A habit tracker app which treats your goals like a Role Playing Game.",
"version": "4.146.7", "version": "4.156.3",
"main": "./website/server/index.js", "main": "./website/server/index.js",
"dependencies": { "dependencies": {
"@babel/core": "^7.10.3", "@babel/core": "^7.11.6",
"@babel/preset-env": "^7.10.3", "@babel/preset-env": "^7.11.5",
"@babel/register": "^7.10.3", "@babel/register": "^7.11.5",
"@google-cloud/trace-agent": "^5.1.0", "@google-cloud/trace-agent": "^5.1.0",
"@slack/client": "^4.12.0", "@slack/client": "^4.12.0",
"accepts": "^1.3.5", "accepts": "^1.3.5",
"amazon-payments": "^0.2.8", "amazon-payments": "^0.2.8",
"amplitude": "^3.5.0", "amplitude": "^3.5.0",
"apidoc": "^0.17.5", "apidoc": "^0.25.0",
"apn": "^2.2.0", "apn": "^2.2.0",
"apple-auth": "^1.0.6", "apple-auth": "^1.0.6",
"bcrypt": "^5.0.0", "bcrypt": "^5.0.0",
@@ -20,7 +20,7 @@
"compression": "^1.7.4", "compression": "^1.7.4",
"cookie-session": "^1.4.0", "cookie-session": "^1.4.0",
"coupon-code": "^0.4.5", "coupon-code": "^0.4.5",
"csv-stringify": "^5.5.0", "csv-stringify": "^5.5.1",
"cwait": "^1.1.1", "cwait": "^1.1.1",
"domain-middleware": "~0.1.0", "domain-middleware": "~0.1.0",
"eslint": "^6.8.0", "eslint": "^6.8.0",
@@ -30,49 +30,51 @@
"express-basic-auth": "^1.1.5", "express-basic-auth": "^1.1.5",
"express-validator": "^5.2.0", "express-validator": "^5.2.0",
"glob": "^7.1.6", "glob": "^7.1.6",
"got": "^11.3.0", "got": "^11.6.0",
"gulp": "^4.0.0", "gulp": "^4.0.0",
"gulp-babel": "^8.0.0", "gulp-babel": "^8.0.0",
"gulp-imagemin": "^7.1.0", "gulp-imagemin": "^7.1.0",
"gulp-nodemon": "^2.5.0", "gulp-nodemon": "^2.5.0",
"gulp.spritesmith": "^6.9.0", "gulp.spritesmith": "^6.9.0",
"habitica-markdown": "^2.0.2", "habitica-markdown": "^3.0.0",
"helmet": "^3.23.1", "helmet": "^3.23.3",
"image-size": "^0.8.3", "image-size": "^0.9.1",
"in-app-purchase": "^1.11.3", "in-app-purchase": "^1.11.3",
"js2xmlparser": "^4.0.1", "js2xmlparser": "^4.0.1",
"jsonwebtoken": "^8.5.1", "jsonwebtoken": "^8.5.1",
"jwks-rsa": "^1.8.1", "jwks-rsa": "^1.9.0",
"lodash": "^4.17.15", "lodash": "^4.17.20",
"merge-stream": "^2.0.0", "merge-stream": "^2.0.0",
"method-override": "^3.0.0", "method-override": "^3.0.0",
"moment": "^2.27.0", "moment": "^2.27.0",
"moment-recur": "^1.0.7", "moment-recur": "^1.0.7",
"mongoose": "^5.9.19", "mongoose": "^5.10.3",
"morgan": "^1.10.0", "morgan": "^1.10.0",
"nconf": "^0.10.0", "nconf": "^0.10.0",
"node-gcm": "^1.0.2", "node-gcm": "^1.0.3",
"on-headers": "^1.0.2", "on-headers": "^1.0.2",
"passport": "^0.4.1", "passport": "^0.4.1",
"passport-facebook": "^3.0.0", "passport-facebook": "^3.0.0",
"passport-google-oauth2": "^0.2.0", "passport-google-oauth2": "^0.2.0",
"passport-google-oauth20": "1.0.0", "passport-google-oauth20": "1.0.0",
"paypal-ipn": "3.0.0",
"paypal-rest-sdk": "^1.8.1", "paypal-rest-sdk": "^1.8.1",
"pp-ipn": "^1.1.0",
"ps-tree": "^1.0.0", "ps-tree": "^1.0.0",
"regenerator-runtime": "^0.13.5", "rate-limiter-flexible": "^2.1.10",
"redis": "^3.0.2",
"regenerator-runtime": "^0.13.7",
"remove-markdown": "^0.3.0", "remove-markdown": "^0.3.0",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"short-uuid": "^3.0.0", "short-uuid": "^3.0.0",
"stripe": "^7.15.0", "stripe": "^7.15.0",
"superagent": "^5.2.2", "superagent": "^6.1.0",
"universal-analytics": "^0.4.17", "universal-analytics": "^0.4.23",
"useragent": "^2.1.9", "useragent": "^2.1.9",
"uuid": "^8.1.0", "uuid": "^8.3.0",
"validator": "^13.1.1", "validator": "^13.1.1",
"vinyl-buffer": "^1.0.1", "vinyl-buffer": "^1.0.1",
"winston": "^3.3.0", "winston": "^3.3.3",
"winston-loggly-bulk": "^3.1.0", "winston-loggly-bulk": "^3.1.1",
"xml2js": "^0.4.23" "xml2js": "^0.4.23"
}, },
"private": true, "private": true,
@@ -102,6 +104,7 @@
"client:unit": "cd website/client && npm run test:unit", "client:unit": "cd website/client && npm run test:unit",
"start": "gulp nodemon", "start": "gulp nodemon",
"debug": "gulp nodemon --inspect", "debug": "gulp nodemon --inspect",
"mongo:dev": "run-rs -v 4.2.8 -l ubuntu1804 --keep --dbpath mongodb-data --number 1 --quiet",
"postinstall": "gulp build && cd website/client && npm install", "postinstall": "gulp build && cd website/client && npm install",
"apidoc": "gulp apidoc" "apidoc": "gulp apidoc"
}, },
@@ -109,13 +112,16 @@
"axios": "^0.19.2", "axios": "^0.19.2",
"chai": "^4.1.2", "chai": "^4.1.2",
"chai-as-promised": "^7.1.1", "chai-as-promised": "^7.1.1",
"chai-moment": "^0.1.0",
"chalk": "^4.1.0", "chalk": "^4.1.0",
"cross-spawn": "^7.0.3",
"expect.js": "^0.3.1", "expect.js": "^0.3.1",
"istanbul": "^1.1.0-alpha.1", "istanbul": "^1.1.0-alpha.1",
"mocha": "^5.1.1", "mocha": "^5.1.1",
"monk": "^7.3.0", "monk": "^7.3.2",
"require-again": "^2.0.0", "require-again": "^2.0.0",
"sinon": "^9.0.2", "run-rs": "^0.6.2",
"sinon": "^9.0.3",
"sinon-chai": "^3.5.0", "sinon-chai": "^3.5.0",
"sinon-stub-promise": "^4.0.0" "sinon-stub-promise": "^4.0.0"
}, },

View File

@@ -31,20 +31,22 @@ async function deleteAmplitudeData (userId, email) {
console.log(`${userId} (${email}) Amplitude response: ${response.status} ${response.statusText}`); console.log(`${userId} (${email}) Amplitude response: ${response.status} ${response.statusText}`);
} }
} }
await new Promise(resolve => setTimeout(resolve, 1000));
} }
async function deleteHabiticaData (user, email) { async function deleteHabiticaData (user, email) {
const truncatedEmail = email.slice(0, email.indexOf('@')); const truncatedEmail = email.slice(0, email.indexOf('@'));
const set = {
'auth.blocked': false,
'auth.local.hashed_password': '$2a$10$QDnNh1j1yMPnTXDEOV38xOePEWFd4X8DSYwAM8XTmqmacG5X0DKjW',
'auth.local.passwordHashMethod': 'bcrypt',
};
if (!user.auth.local.email) set['auth.local.email'] = `${truncatedEmail}-gdpr@example.com`;
await User.update( await User.update(
{ _id: user._id }, { _id: user._id },
{ { $set: set },
$set: {
'auth.local.email': user.auth.local.email ? email : `${truncatedEmail}@example.com`,
'auth.local.hashed_password': '$2a$10$QDnNh1j1yMPnTXDEOV38xOePEWFd4X8DSYwAM8XTmqmacG5X0DKjW',
'auth.local.passwordHashMethod': 'bcrypt',
},
},
); );
await new Promise(resolve => setTimeout(resolve, 1000));
const response = await axios.delete( const response = await axios.delete(
`${BASE_URL}/api/v3/user`, `${BASE_URL}/api/v3/user`,
{ {
@@ -78,6 +80,7 @@ async function processEmailAddress (email) {
const socialUsers = await User.find( const socialUsers = await User.find(
{ {
'auth.local.email': { $not: emailRegex },
$or: [ $or: [
{ 'auth.facebook.emails.value': email }, { 'auth.facebook.emails.value': email },
{ 'auth.google.emails.value': email }, { 'auth.google.emails.value': email },

View File

@@ -56,7 +56,6 @@ describe('Base model plugin', () => {
expect(sanitized).not.to.have.property('usuallySettable'); expect(sanitized).not.to.have.property('usuallySettable');
}); });
it('can make fields private', () => { it('can make fields private', () => {
schema.plugin(baseModel, { schema.plugin(baseModel, {
private: ['amPrivate'], private: ['amPrivate'],

View File

@@ -42,13 +42,13 @@ describe('cron', () => {
}); });
it('updates user.preferences.timezoneOffsetAtLastCron', () => { it('updates user.preferences.timezoneOffsetAtLastCron', () => {
const timezoneOffsetFromUserPrefs = 1; const timezoneUtcOffsetFromUserPrefs = -1;
cron({ cron({
user, tasksByType, daysMissed, analytics, timezoneOffsetFromUserPrefs, user, tasksByType, daysMissed, analytics, timezoneUtcOffsetFromUserPrefs,
}); });
expect(user.preferences.timezoneOffsetAtLastCron).to.equal(timezoneOffsetFromUserPrefs); expect(user.preferences.timezoneOffsetAtLastCron).to.equal(1);
}); });
it('resets user.items.lastDrop.count', () => { it('resets user.items.lastDrop.count', () => {
@@ -240,7 +240,7 @@ describe('cron', () => {
user1.purchased.plan.consecutive.gemCapExtra = 0; user1.purchased.plan.consecutive.gemCapExtra = 0;
it('does not increment consecutive benefits after the first month', () => { it('does not increment consecutive benefits after the first month', () => {
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(1, 'months') clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(1, 'months')
.add(2, 'days') .add(2, 'days')
.toDate()); .toDate());
// Add 1 month to simulate what happens a month after the subscription was created. // Add 1 month to simulate what happens a month after the subscription was created.
@@ -256,7 +256,7 @@ describe('cron', () => {
}); });
it('does not increment consecutive benefits after the second month', () => { it('does not increment consecutive benefits after the second month', () => {
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(2, 'months') clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(2, 'months')
.add(2, 'days') .add(2, 'days')
.toDate()); .toDate());
// Add 1 month to simulate what happens a month after the subscription was created. // Add 1 month to simulate what happens a month after the subscription was created.
@@ -272,7 +272,7 @@ describe('cron', () => {
}); });
it('increments consecutive benefits after the third month', () => { it('increments consecutive benefits after the third month', () => {
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(3, 'months') clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(3, 'months')
.add(2, 'days') .add(2, 'days')
.toDate()); .toDate());
// Add 1 month to simulate what happens a month after the subscription was created. // Add 1 month to simulate what happens a month after the subscription was created.
@@ -288,7 +288,7 @@ describe('cron', () => {
}); });
it('does not increment consecutive benefits after the fourth month', () => { it('does not increment consecutive benefits after the fourth month', () => {
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(4, 'months') clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(4, 'months')
.add(2, 'days') .add(2, 'days')
.toDate()); .toDate());
// Add 1 month to simulate what happens a month after the subscription was created. // Add 1 month to simulate what happens a month after the subscription was created.
@@ -304,7 +304,7 @@ describe('cron', () => {
}); });
it('increments consecutive benefits correctly if user has been absent with continuous subscription', () => { it('increments consecutive benefits correctly if user has been absent with continuous subscription', () => {
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(10, 'months') clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(10, 'months')
.add(2, 'days') .add(2, 'days')
.toDate()); .toDate());
cron({ cron({
@@ -339,7 +339,7 @@ describe('cron', () => {
user3.purchased.plan.consecutive.gemCapExtra = 5; user3.purchased.plan.consecutive.gemCapExtra = 5;
it('does not increment consecutive benefits in the first month of the first paid period that they already have benefits for', () => { it('does not increment consecutive benefits in the first month of the first paid period that they already have benefits for', () => {
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(1, 'months') clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(1, 'months')
.add(2, 'days') .add(2, 'days')
.toDate()); .toDate());
cron({ cron({
@@ -352,7 +352,7 @@ describe('cron', () => {
}); });
it('does not increment consecutive benefits in the middle of the period that they already have benefits for', () => { it('does not increment consecutive benefits in the middle of the period that they already have benefits for', () => {
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(2, 'months') clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(2, 'months')
.add(2, 'days') .add(2, 'days')
.toDate()); .toDate());
cron({ cron({
@@ -365,7 +365,7 @@ describe('cron', () => {
}); });
it('does not increment consecutive benefits in the final month of the period that they already have benefits for', () => { it('does not increment consecutive benefits in the final month of the period that they already have benefits for', () => {
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(3, 'months') clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(3, 'months')
.add(2, 'days') .add(2, 'days')
.toDate()); .toDate());
cron({ cron({
@@ -378,7 +378,7 @@ describe('cron', () => {
}); });
it('increments consecutive benefits the month after the second paid period has started', () => { it('increments consecutive benefits the month after the second paid period has started', () => {
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(4, 'months') clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(4, 'months')
.add(2, 'days') .add(2, 'days')
.toDate()); .toDate());
cron({ cron({
@@ -391,7 +391,7 @@ describe('cron', () => {
}); });
it('does not increment consecutive benefits in the second month of the second period that they already have benefits for', () => { it('does not increment consecutive benefits in the second month of the second period that they already have benefits for', () => {
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(5, 'months') clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(5, 'months')
.add(2, 'days') .add(2, 'days')
.toDate()); .toDate());
cron({ cron({
@@ -404,7 +404,7 @@ describe('cron', () => {
}); });
it('does not increment consecutive benefits in the final month of the second period that they already have benefits for', () => { it('does not increment consecutive benefits in the final month of the second period that they already have benefits for', () => {
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(6, 'months') clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(6, 'months')
.add(2, 'days') .add(2, 'days')
.toDate()); .toDate());
cron({ cron({
@@ -417,7 +417,7 @@ describe('cron', () => {
}); });
it('increments consecutive benefits the month after the third paid period has started', () => { it('increments consecutive benefits the month after the third paid period has started', () => {
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(7, 'months') clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(7, 'months')
.add(2, 'days') .add(2, 'days')
.toDate()); .toDate());
cron({ cron({
@@ -430,7 +430,7 @@ describe('cron', () => {
}); });
it('increments consecutive benefits correctly if user has been absent with continuous subscription', () => { it('increments consecutive benefits correctly if user has been absent with continuous subscription', () => {
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(10, 'months') clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(10, 'months')
.add(2, 'days') .add(2, 'days')
.toDate()); .toDate());
cron({ cron({
@@ -465,7 +465,7 @@ describe('cron', () => {
user6.purchased.plan.consecutive.gemCapExtra = 10; user6.purchased.plan.consecutive.gemCapExtra = 10;
it('does not increment consecutive benefits in the first month of the first paid period that they already have benefits for', () => { it('does not increment consecutive benefits in the first month of the first paid period that they already have benefits for', () => {
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(1, 'months') clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(1, 'months')
.add(2, 'days') .add(2, 'days')
.toDate()); .toDate());
cron({ cron({
@@ -478,7 +478,7 @@ describe('cron', () => {
}); });
it('does not increment consecutive benefits in the final month of the period that they already have benefits for', () => { it('does not increment consecutive benefits in the final month of the period that they already have benefits for', () => {
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(6, 'months') clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(6, 'months')
.add(2, 'days') .add(2, 'days')
.toDate()); .toDate());
cron({ cron({
@@ -491,7 +491,7 @@ describe('cron', () => {
}); });
it('increments consecutive benefits the month after the second paid period has started', () => { it('increments consecutive benefits the month after the second paid period has started', () => {
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(7, 'months') clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(7, 'months')
.add(2, 'days') .add(2, 'days')
.toDate()); .toDate());
cron({ cron({
@@ -504,7 +504,7 @@ describe('cron', () => {
}); });
it('increments consecutive benefits the month after the third paid period has started', () => { it('increments consecutive benefits the month after the third paid period has started', () => {
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(13, 'months') clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(13, 'months')
.add(2, 'days') .add(2, 'days')
.toDate()); .toDate());
cron({ cron({
@@ -517,7 +517,7 @@ describe('cron', () => {
}); });
it('increments consecutive benefits correctly if user has been absent with continuous subscription', () => { it('increments consecutive benefits correctly if user has been absent with continuous subscription', () => {
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(19, 'months') clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(19, 'months')
.add(2, 'days') .add(2, 'days')
.toDate()); .toDate());
cron({ cron({
@@ -552,7 +552,7 @@ describe('cron', () => {
user12.purchased.plan.consecutive.gemCapExtra = 20; user12.purchased.plan.consecutive.gemCapExtra = 20;
it('does not increment consecutive benefits in the first month of the first paid period that they already have benefits for', () => { it('does not increment consecutive benefits in the first month of the first paid period that they already have benefits for', () => {
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(1, 'months') clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(1, 'months')
.add(2, 'days') .add(2, 'days')
.toDate()); .toDate());
cron({ cron({
@@ -565,7 +565,7 @@ describe('cron', () => {
}); });
it('does not increment consecutive benefits in the final month of the period that they already have benefits for', () => { it('does not increment consecutive benefits in the final month of the period that they already have benefits for', () => {
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(12, 'months') clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(12, 'months')
.add(2, 'days') .add(2, 'days')
.toDate()); .toDate());
cron({ cron({
@@ -578,7 +578,7 @@ describe('cron', () => {
}); });
it('increments consecutive benefits the month after the second paid period has started', () => { it('increments consecutive benefits the month after the second paid period has started', () => {
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(13, 'months') clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(13, 'months')
.add(2, 'days') .add(2, 'days')
.toDate()); .toDate());
cron({ cron({
@@ -591,7 +591,7 @@ describe('cron', () => {
}); });
it('increments consecutive benefits the month after the third paid period has started', () => { it('increments consecutive benefits the month after the third paid period has started', () => {
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(25, 'months') clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(25, 'months')
.add(2, 'days') .add(2, 'days')
.toDate()); .toDate());
cron({ cron({
@@ -604,7 +604,7 @@ describe('cron', () => {
}); });
it('increments consecutive benefits correctly if user has been absent with continuous subscription', () => { it('increments consecutive benefits correctly if user has been absent with continuous subscription', () => {
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(37, 'months') clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(37, 'months')
.add(2, 'days') .add(2, 'days')
.toDate()); .toDate());
cron({ cron({
@@ -641,7 +641,7 @@ describe('cron', () => {
user3g.purchased.plan.consecutive.gemCapExtra = 5; user3g.purchased.plan.consecutive.gemCapExtra = 5;
it('does not increment consecutive benefits in the first month of the gift subscription', () => { it('does not increment consecutive benefits in the first month of the gift subscription', () => {
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(1, 'months') clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(1, 'months')
.add(2, 'days') .add(2, 'days')
.toDate()); .toDate());
cron({ cron({
@@ -654,7 +654,7 @@ describe('cron', () => {
}); });
it('does not increment consecutive benefits in the second month of the gift subscription', () => { it('does not increment consecutive benefits in the second month of the gift subscription', () => {
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(2, 'months') clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(2, 'months')
.add(2, 'days') .add(2, 'days')
.toDate()); .toDate());
cron({ cron({
@@ -667,7 +667,7 @@ describe('cron', () => {
}); });
it('does not increment consecutive benefits in the third month of the gift subscription', () => { it('does not increment consecutive benefits in the third month of the gift subscription', () => {
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(3, 'months') clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(3, 'months')
.add(2, 'days') .add(2, 'days')
.toDate()); .toDate());
cron({ cron({
@@ -680,7 +680,7 @@ describe('cron', () => {
}); });
it('does not increment consecutive benefits in the month after the gift subscription has ended', () => { it('does not increment consecutive benefits in the month after the gift subscription has ended', () => {
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(4, 'months') clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(4, 'months')
.add(2, 'days') .add(2, 'days')
.toDate()); .toDate());
cron({ cron({
@@ -717,7 +717,7 @@ describe('cron', () => {
user6x.purchased.plan.consecutive.gemCapExtra = 15; user6x.purchased.plan.consecutive.gemCapExtra = 15;
it('increments consecutive benefits in the first month since the fix for #4819 goes live', () => { it('increments consecutive benefits in the first month since the fix for #4819 goes live', () => {
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(1, 'months') clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(1, 'months')
.add(2, 'days') .add(2, 'days')
.toDate()); .toDate());
cron({ cron({
@@ -730,7 +730,7 @@ describe('cron', () => {
}); });
it('does not increment consecutive benefits in the second month after the fix goes live', () => { it('does not increment consecutive benefits in the second month after the fix goes live', () => {
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(2, 'months') clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(2, 'months')
.add(2, 'days') .add(2, 'days')
.toDate()); .toDate());
cron({ cron({
@@ -743,7 +743,7 @@ describe('cron', () => {
}); });
it('does not increment consecutive benefits in the third month after the fix goes live', () => { it('does not increment consecutive benefits in the third month after the fix goes live', () => {
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(3, 'months') clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(3, 'months')
.add(2, 'days') .add(2, 'days')
.toDate()); .toDate());
cron({ cron({
@@ -756,7 +756,7 @@ describe('cron', () => {
}); });
it('increments consecutive benefits in the seventh month after the fix goes live', () => { it('increments consecutive benefits in the seventh month after the fix goes live', () => {
clock = sinon.useFakeTimers(moment().zone(0).startOf('month').add(7, 'months') clock = sinon.useFakeTimers(moment().utcOffset(0).startOf('month').add(7, 'months')
.add(2, 'days') .add(2, 'days')
.toDate()); .toDate());
cron({ cron({

View File

@@ -0,0 +1,50 @@
import os from 'os';
import nconf from 'nconf';
import requireAgain from 'require-again';
const pathToMongoLib = '../../../../website/server/libs/mongodb';
describe('mongodb', () => {
afterEach(() => {
sandbox.restore();
});
describe('getDevelopmentConnectionUrl', () => {
it('returns the original connection url if not on windows', () => {
sandbox.stub(os, 'platform').returns('linux');
const mongoLibOverride = requireAgain(pathToMongoLib);
const originalString = 'mongodb://localhost:3030';
const string = mongoLibOverride.getDevelopmentConnectionUrl(originalString);
expect(string).to.equal(originalString);
});
it('replaces localhost with hostname on windows', () => {
sandbox.stub(os, 'platform').returns('win32');
sandbox.stub(os, 'hostname').returns('hostname');
const mongoLibOverride = requireAgain(pathToMongoLib);
const originalString = 'mongodb://localhost:3030';
const string = mongoLibOverride.getDevelopmentConnectionUrl(originalString);
expect(string).to.equal('mongodb://hostname:3030');
});
});
describe('getDefaultConnectionOptions', () => {
it('returns development config when IS_PROD is false', () => {
sandbox.stub(nconf, 'get').withArgs('IS_PROD').returns(false);
const mongoLibOverride = requireAgain(pathToMongoLib);
const options = mongoLibOverride.getDefaultConnectionOptions();
expect(options).to.have.all.keys(['useNewUrlParser', 'useUnifiedTopology']);
});
it('returns production config when IS_PROD is true', () => {
sandbox.stub(nconf, 'get').withArgs('IS_PROD').returns(true);
const mongoLibOverride = requireAgain(pathToMongoLib);
const options = mongoLibOverride.getDefaultConnectionOptions();
expect(options).to.have.all.keys(['useNewUrlParser', 'useUnifiedTopology', 'keepAlive', 'keepAliveInitialDelay']);
});
});
});

View File

@@ -7,7 +7,6 @@ import amzLib from '../../../../../../website/server/libs/payments/amazon';
import payments from '../../../../../../website/server/libs/payments/payments'; import payments from '../../../../../../website/server/libs/payments/payments';
import common from '../../../../../../website/common'; import common from '../../../../../../website/common';
describe('#upgradeGroupPlan', () => { describe('#upgradeGroupPlan', () => {
let spy; let data; let user; let group; let let spy; let data; let user; let group; let
uuidString; uuidString;
@@ -32,11 +31,14 @@ describe('#upgradeGroupPlan', () => {
group = generateGroup({ group = generateGroup({
name: 'test group', name: 'test group',
type: 'guild', type: 'guild',
privacy: 'public', privacy: 'private',
leader: user._id, leader: user._id,
}); });
await group.save(); await group.save();
user.guilds.push(group._id);
await user.save();
spy = sinon.stub(amzLib, 'authorizeOnBillingAgreement'); spy = sinon.stub(amzLib, 'authorizeOnBillingAgreement');
spy.resolves([]); spy.resolves([]);

View File

@@ -21,11 +21,14 @@ describe('Canceling a subscription for group', () => {
group = generateGroup({ group = generateGroup({
name: 'test group', name: 'test group',
type: 'guild', type: 'guild',
privacy: 'public', privacy: 'private',
leader: user._id, leader: user._id,
}); });
await group.save(); await group.save();
user.guilds.push(group._id);
await user.save();
data = { data = {
user, user,
sub: { sub: {
@@ -141,6 +144,8 @@ describe('Canceling a subscription for group', () => {
it('prevents non group leader from managing subscription', async () => { it('prevents non group leader from managing subscription', async () => {
const groupMember = new User(); const groupMember = new User();
groupMember.guilds.push(group._id);
await groupMember.save();
data.user = groupMember; data.user = groupMember;
data.groupId = group._id; data.groupId = group._id;
@@ -162,7 +167,9 @@ describe('Canceling a subscription for group', () => {
let updatedGroup = await Group.findById(group._id).exec(); let updatedGroup = await Group.findById(group._id).exec();
const newLeader = new User(); const newLeader = new User();
newLeader.profile.name = 'newLeader';
updatedGroup.leader = newLeader._id; updatedGroup.leader = newLeader._id;
await newLeader.save();
await updatedGroup.save(); await updatedGroup.save();
await api.cancelSubscription(data); await api.cancelSubscription(data);
@@ -185,8 +192,6 @@ describe('Canceling a subscription for group', () => {
'user-agent': '', 'user-agent': '',
}, },
}; };
user.guilds.push(group._id);
await user.save();
expect(group.purchased.plan.planId).to.not.exist; expect(group.purchased.plan.planId).to.not.exist;
data.groupId = group._id; data.groupId = group._id;
await api.createSubscription(data); await api.createSubscription(data);
@@ -211,10 +216,15 @@ describe('Canceling a subscription for group', () => {
await api.createSubscription(data); await api.createSubscription(data);
await api.cancelSubscription(data); await api.cancelSubscription(data);
expect(sender.sendTxn).to.be.have.callCount(4); expect(sender.sendTxn).to.be.have.callCount(6);
expect(sender.sendTxn.thirdCall.args[0]._id).to.equal(recipient._id); const recipientCall = sender.sendTxn.getCalls().find(call => {
expect(sender.sendTxn.thirdCall.args[1]).to.equal('group-member-cancel'); const isRecipient = call.args[0]._id === recipient._id;
expect(sender.sendTxn.thirdCall.args[2]).to.eql([ const isGroupMemberCancel = call.args[1] === 'group-member-cancel';
return isRecipient && isGroupMemberCancel;
});
expect(recipientCall.args[0]._id).to.equal(recipient._id);
expect(recipientCall.args[1]).to.equal('group-member-cancel');
expect(recipientCall.args[2]).to.eql([
{ name: 'LEADER', content: user.profile.name }, { name: 'LEADER', content: user.profile.name },
{ name: 'GROUP_NAME', content: group.name }, { name: 'GROUP_NAME', content: group.name },
]); ]);
@@ -246,8 +256,6 @@ describe('Canceling a subscription for group', () => {
recipient.guilds.push(group._id); recipient.guilds.push(group._id);
await recipient.save(); await recipient.save();
user.guilds.push(group._id);
await user.save();
data.groupId = group._id; data.groupId = group._id;
await api.createSubscription(data); await api.createSubscription(data);
@@ -259,11 +267,13 @@ describe('Canceling a subscription for group', () => {
const group2 = generateGroup({ const group2 = generateGroup({
name: 'test group2', name: 'test group2',
type: 'guild', type: 'guild',
privacy: 'public', privacy: 'private',
leader: user._id, leader: user._id,
}); });
data.groupId = group2._id; data.groupId = group2._id;
await group2.save(); await group2.save();
user.guilds.push(group2._id);
await user.save();
recipient.guilds.push(group2._id); recipient.guilds.push(group2._id);
await recipient.save(); await recipient.save();
@@ -285,8 +295,6 @@ describe('Canceling a subscription for group', () => {
}); });
it('does cancel a leader subscription with two cancelled group plans', async () => { it('does cancel a leader subscription with two cancelled group plans', async () => {
user.guilds.push(group._id);
await user.save();
data.groupId = group._id; data.groupId = group._id;
await api.createSubscription(data); await api.createSubscription(data);
@@ -298,7 +306,7 @@ describe('Canceling a subscription for group', () => {
const group2 = generateGroup({ const group2 = generateGroup({
name: 'test group2', name: 'test group2',
type: 'guild', type: 'guild',
privacy: 'public', privacy: 'private',
leader: user._id, leader: user._id,
}); });
user.guilds.push(group2._id); user.guilds.push(group2._id);

View File

@@ -12,6 +12,7 @@ import { model as Group } from '../../../../../../website/server/models/group';
import { import {
generateGroup, generateGroup,
} from '../../../../../helpers/api-unit.helper'; } from '../../../../../helpers/api-unit.helper';
import i18n from '../../../../../../website/common/script/i18n';
describe('Purchasing a group plan for group', () => { describe('Purchasing a group plan for group', () => {
const EMAIL_TEMPLATE_SUBSCRIPTION_TYPE_GOOGLE = 'Google_subscription'; const EMAIL_TEMPLATE_SUBSCRIPTION_TYPE_GOOGLE = 'Google_subscription';
@@ -33,11 +34,14 @@ describe('Purchasing a group plan for group', () => {
group = generateGroup({ group = generateGroup({
name: groupName, name: groupName,
type: 'guild', type: 'guild',
privacy: 'public', privacy: 'private',
leader: user._id, leader: user._id,
}); });
await group.save(); await group.save();
user.guilds.push(group._id);
await user.save();
data = { data = {
user, user,
sub: { sub: {
@@ -112,6 +116,30 @@ describe('Purchasing a group plan for group', () => {
expect(updatedGroup.purchased.plan.dateCreated).to.exist; expect(updatedGroup.purchased.plan.dateCreated).to.exist;
}); });
it('does not create a group plan for a public guild', async () => {
const publicGroup = generateGroup({
name: groupName,
type: 'guild',
privacy: 'public',
leader: user._id,
});
await publicGroup.save();
expect(publicGroup.purchased.plan.planId).to.not.exist;
data.groupId = publicGroup._id;
await expect(api.createSubscription(data))
.to.eventually.be.rejected.and.to.eql({
httpCode: 401,
name: 'NotAuthorized',
message: i18n.t('onlyPrivateGuildsCanUpgrade'),
});
const updatedGroup = await Group.findById(publicGroup._id).exec();
expect(updatedGroup.purchased.plan.planId).to.not.exist;
});
it('sends an email', async () => { it('sends an email', async () => {
expect(group.purchased.plan.planId).to.not.exist; expect(group.purchased.plan.planId).to.not.exist;
data.groupId = group._id; data.groupId = group._id;
@@ -148,8 +176,6 @@ describe('Purchasing a group plan for group', () => {
}); });
it('grants all members of a group a subscription', async () => { it('grants all members of a group a subscription', async () => {
user.guilds.push(group._id);
await user.save();
expect(group.purchased.plan.planId).to.not.exist; expect(group.purchased.plan.planId).to.not.exist;
data.groupId = group._id; data.groupId = group._id;
@@ -179,17 +205,28 @@ describe('Purchasing a group plan for group', () => {
await api.createSubscription(data); await api.createSubscription(data);
expect(sender.sendTxn).to.be.calledTwice; expect(sender.sendTxn).to.be.calledThrice;
expect(sender.sendTxn.firstCall.args[0]._id).to.equal(recipient._id); const recipientCall = sender.sendTxn.getCalls().find(call => {
expect(sender.sendTxn.firstCall.args[1]).to.equal('group-member-join'); const isRecipient = call.args[0]._id === recipient._id;
expect(sender.sendTxn.firstCall.args[2]).to.eql([ const isJoin = call.args[1] === 'group-member-join';
return isRecipient && isJoin;
});
expect(recipientCall.args[0]._id).to.equal(recipient._id);
expect(recipientCall.args[1]).to.equal('group-member-join');
expect(recipientCall.args[2]).to.eql([
{ name: 'LEADER', content: user.profile.name }, { name: 'LEADER', content: user.profile.name },
{ name: 'GROUP_NAME', content: group.name }, { name: 'GROUP_NAME', content: group.name },
{ name: 'PREVIOUS_SUBSCRIPTION_TYPE', content: EMAIL_TEMPLATE_SUBSCRIPTION_TYPE_NONE }, { name: 'PREVIOUS_SUBSCRIPTION_TYPE', content: EMAIL_TEMPLATE_SUBSCRIPTION_TYPE_NONE },
]); ]);
// confirm that the other email sent is appropriate: // confirm that the other email sent is appropriate:
expect(sender.sendTxn.secondCall.args[0]._id).to.equal(group.leader); const leaderCall = sender.sendTxn.getCalls().find(call => {
expect(sender.sendTxn.secondCall.args[1]).to.equal('group-subscription-begins'); const isLeader = call.args[0]._id === group.leader;
const isSubscriptionBegin = call.args[1] === 'group-subscription-begins';
return isLeader && isSubscriptionBegin;
});
expect(leaderCall.args[0]._id).to.equal(group.leader);
expect(leaderCall.args[1]).to.equal('group-subscription-begins');
}); });
it('sends one email to subscribed member of group, stating subscription is cancelled (Stripe)', async () => { it('sends one email to subscribed member of group, stating subscription is cancelled (Stripe)', async () => {
@@ -205,17 +242,28 @@ describe('Purchasing a group plan for group', () => {
await api.createSubscription(data); await api.createSubscription(data);
expect(sender.sendTxn).to.be.calledTwice; expect(sender.sendTxn).to.be.calledThrice;
expect(sender.sendTxn.firstCall.args[0]._id).to.equal(recipient._id); const recipientCall = sender.sendTxn.getCalls().find(call => {
expect(sender.sendTxn.firstCall.args[1]).to.equal('group-member-join'); const isRecipient = call.args[0]._id === recipient._id;
expect(sender.sendTxn.firstCall.args[2]).to.eql([ const isJoin = call.args[1] === 'group-member-join';
return isRecipient && isJoin;
});
expect(recipientCall.args[0]._id).to.equal(recipient._id);
expect(recipientCall.args[1]).to.equal('group-member-join');
expect(recipientCall.args[2]).to.eql([
{ name: 'LEADER', content: user.profile.name }, { name: 'LEADER', content: user.profile.name },
{ name: 'GROUP_NAME', content: group.name }, { name: 'GROUP_NAME', content: group.name },
{ name: 'PREVIOUS_SUBSCRIPTION_TYPE', content: EMAIL_TEMPLATE_SUBSCRIPTION_TYPE_NORMAL }, { name: 'PREVIOUS_SUBSCRIPTION_TYPE', content: EMAIL_TEMPLATE_SUBSCRIPTION_TYPE_NORMAL },
]); ]);
// confirm that the other email sent is not a cancel-subscription email: // confirm that the other email sent is not a cancel-subscription email:
expect(sender.sendTxn.secondCall.args[0]._id).to.equal(group.leader); const leaderCall = sender.sendTxn.getCalls().find(call => {
expect(sender.sendTxn.secondCall.args[1]).to.equal('group-subscription-begins'); const isLeader = call.args[0]._id === group.leader;
const isSubscriptionBegin = call.args[1] === 'group-subscription-begins';
return isLeader && isSubscriptionBegin;
});
expect(leaderCall.args[0]._id).to.equal(group.leader);
expect(leaderCall.args[1]).to.equal('group-subscription-begins');
}); });
it('sends one email to subscribed member of group, stating subscription is cancelled (Amazon)', async () => { it('sends one email to subscribed member of group, stating subscription is cancelled (Amazon)', async () => {
@@ -238,17 +286,28 @@ describe('Purchasing a group plan for group', () => {
await api.createSubscription(data); await api.createSubscription(data);
expect(sender.sendTxn).to.be.calledTwice; expect(sender.sendTxn).to.be.calledThrice;
expect(sender.sendTxn.firstCall.args[0]._id).to.equal(recipient._id); const recipientCall = sender.sendTxn.getCalls().find(call => {
expect(sender.sendTxn.firstCall.args[1]).to.equal('group-member-join'); const isRecipient = call.args[0]._id === recipient._id;
expect(sender.sendTxn.firstCall.args[2]).to.eql([ const isJoin = call.args[1] === 'group-member-join';
return isRecipient && isJoin;
});
expect(recipientCall.args[0]._id).to.equal(recipient._id);
expect(recipientCall.args[1]).to.equal('group-member-join');
expect(recipientCall.args[2]).to.eql([
{ name: 'LEADER', content: user.profile.name }, { name: 'LEADER', content: user.profile.name },
{ name: 'GROUP_NAME', content: group.name }, { name: 'GROUP_NAME', content: group.name },
{ name: 'PREVIOUS_SUBSCRIPTION_TYPE', content: EMAIL_TEMPLATE_SUBSCRIPTION_TYPE_NORMAL }, { name: 'PREVIOUS_SUBSCRIPTION_TYPE', content: EMAIL_TEMPLATE_SUBSCRIPTION_TYPE_NORMAL },
]); ]);
// confirm that the other email sent is not a cancel-subscription email: // confirm that the other email sent is not a cancel-subscription email:
expect(sender.sendTxn.secondCall.args[0]._id).to.equal(group.leader); const leaderCall = sender.sendTxn.getCalls().find(call => {
expect(sender.sendTxn.secondCall.args[1]).to.equal('group-subscription-begins'); const isLeader = call.args[0]._id === group.leader;
const isSubscriptionBegin = call.args[1] === 'group-subscription-begins';
return isLeader && isSubscriptionBegin;
});
expect(leaderCall.args[0]._id).to.equal(group.leader);
expect(leaderCall.args[1]).to.equal('group-subscription-begins');
amzLib.getBillingAgreementDetails.restore(); amzLib.getBillingAgreementDetails.restore();
}); });
@@ -275,17 +334,28 @@ describe('Purchasing a group plan for group', () => {
await api.createSubscription(data); await api.createSubscription(data);
expect(sender.sendTxn).to.be.calledTwice; expect(sender.sendTxn).to.be.calledThrice;
expect(sender.sendTxn.firstCall.args[0]._id).to.equal(recipient._id); const recipientCall = sender.sendTxn.getCalls().find(call => {
expect(sender.sendTxn.firstCall.args[1]).to.equal('group-member-join'); const isRecipient = call.args[0]._id === recipient._id;
expect(sender.sendTxn.firstCall.args[2]).to.eql([ const isJoin = call.args[1] === 'group-member-join';
return isRecipient && isJoin;
});
expect(recipientCall.args[0]._id).to.equal(recipient._id);
expect(recipientCall.args[1]).to.equal('group-member-join');
expect(recipientCall.args[2]).to.eql([
{ name: 'LEADER', content: user.profile.name }, { name: 'LEADER', content: user.profile.name },
{ name: 'GROUP_NAME', content: group.name }, { name: 'GROUP_NAME', content: group.name },
{ name: 'PREVIOUS_SUBSCRIPTION_TYPE', content: EMAIL_TEMPLATE_SUBSCRIPTION_TYPE_NORMAL }, { name: 'PREVIOUS_SUBSCRIPTION_TYPE', content: EMAIL_TEMPLATE_SUBSCRIPTION_TYPE_NORMAL },
]); ]);
// confirm that the other email sent is not a cancel-subscription email: // confirm that the other email sent is not a cancel-subscription email:
expect(sender.sendTxn.secondCall.args[0]._id).to.equal(group.leader); const leaderCall = sender.sendTxn.getCalls().find(call => {
expect(sender.sendTxn.secondCall.args[1]).to.equal('group-subscription-begins'); const isLeader = call.args[0]._id === group.leader;
const isSubscriptionBegin = call.args[1] === 'group-subscription-begins';
return isLeader && isSubscriptionBegin;
});
expect(leaderCall.args[0]._id).to.equal(group.leader);
expect(leaderCall.args[1]).to.equal('group-subscription-begins');
paypalPayments.paypalBillingAgreementGet.restore(); paypalPayments.paypalBillingAgreementGet.restore();
paypalPayments.paypalBillingAgreementCancel.restore(); paypalPayments.paypalBillingAgreementCancel.restore();
@@ -302,8 +372,6 @@ describe('Purchasing a group plan for group', () => {
recipient.guilds.push(group._id); recipient.guilds.push(group._id);
await recipient.save(); await recipient.save();
user.guilds.push(group._id);
await user.save();
data.groupId = group._id; data.groupId = group._id;
await api.createSubscription(data); await api.createSubscription(data);
@@ -356,8 +424,6 @@ describe('Purchasing a group plan for group', () => {
recipient.guilds.push(group._id); recipient.guilds.push(group._id);
await recipient.save(); await recipient.save();
user.guilds.push(group._id);
await user.save();
data.groupId = group._id; data.groupId = group._id;
await api.createSubscription(data); await api.createSubscription(data);
@@ -419,8 +485,6 @@ describe('Purchasing a group plan for group', () => {
data.gift = undefined; data.gift = undefined;
user.guilds.push(group._id);
await user.save();
data.groupId = group._id; data.groupId = group._id;
await api.createSubscription(data); await api.createSubscription(data);
@@ -455,8 +519,6 @@ describe('Purchasing a group plan for group', () => {
data.gift = undefined; data.gift = undefined;
user.guilds.push(group._id);
await user.save();
data.groupId = group._id; data.groupId = group._id;
await api.createSubscription(data); await api.createSubscription(data);
@@ -483,8 +545,6 @@ describe('Purchasing a group plan for group', () => {
recipient.guilds.push(group._id); recipient.guilds.push(group._id);
await recipient.save(); await recipient.save();
user.guilds.push(group._id);
await user.save();
data.groupId = group._id; data.groupId = group._id;
await api.createSubscription(data); await api.createSubscription(data);
@@ -511,8 +571,6 @@ describe('Purchasing a group plan for group', () => {
await recipient.save(); await recipient.save();
user.guilds.push(group._id);
await user.save();
data.groupId = group._id; data.groupId = group._id;
await api.createSubscription(data); await api.createSubscription(data);
@@ -541,8 +599,6 @@ describe('Purchasing a group plan for group', () => {
await recipient.save(); await recipient.save();
user.guilds.push(group._id);
await user.save();
data.groupId = group._id; data.groupId = group._id;
await api.createSubscription(data); await api.createSubscription(data);
@@ -566,8 +622,6 @@ describe('Purchasing a group plan for group', () => {
recipient.guilds.push(group._id); recipient.guilds.push(group._id);
await recipient.save(); await recipient.save();
user.guilds.push(group._id);
await user.save();
data.groupId = group._id; data.groupId = group._id;
await recipient.cancelSubscription(); await recipient.cancelSubscription();
@@ -589,8 +643,6 @@ describe('Purchasing a group plan for group', () => {
recipient.guilds.push(group._id); recipient.guilds.push(group._id);
await recipient.save(); await recipient.save();
user.guilds.push(group._id);
await user.save();
data.groupId = group._id; data.groupId = group._id;
await recipient.cancelSubscription(); await recipient.cancelSubscription();
@@ -611,8 +663,6 @@ describe('Purchasing a group plan for group', () => {
recipient.guilds.push(group._id); recipient.guilds.push(group._id);
await recipient.save(); await recipient.save();
user.guilds.push(group._id);
await user.save();
data.groupId = group._id; data.groupId = group._id;
await api.createSubscription(data); await api.createSubscription(data);
@@ -632,8 +682,6 @@ describe('Purchasing a group plan for group', () => {
recipient.guilds.push(group._id); recipient.guilds.push(group._id);
await recipient.save(); await recipient.save();
user.guilds.push(group._id);
await user.save();
data.groupId = group._id; data.groupId = group._id;
await api.createSubscription(data); await api.createSubscription(data);
@@ -653,8 +701,6 @@ describe('Purchasing a group plan for group', () => {
recipient.guilds.push(group._id); recipient.guilds.push(group._id);
await recipient.save(); await recipient.save();
user.guilds.push(group._id);
await user.save();
data.groupId = group._id; data.groupId = group._id;
await api.createSubscription(data); await api.createSubscription(data);
@@ -688,8 +734,6 @@ describe('Purchasing a group plan for group', () => {
recipient.guilds.push(group._id); recipient.guilds.push(group._id);
await recipient.save(); await recipient.save();
user.guilds.push(group._id);
await user.save();
data.groupId = group._id; data.groupId = group._id;
await api.createSubscription(data); await api.createSubscription(data);
@@ -713,8 +757,6 @@ describe('Purchasing a group plan for group', () => {
recipient.guilds.push(group._id); recipient.guilds.push(group._id);
await recipient.save(); await recipient.save();
user.guilds.push(group._id);
await user.save();
data.groupId = group._id; data.groupId = group._id;
await api.createSubscription(data); await api.createSubscription(data);
@@ -726,13 +768,15 @@ describe('Purchasing a group plan for group', () => {
const group2 = generateGroup({ const group2 = generateGroup({
name: 'test group2', name: 'test group2',
type: 'guild', type: 'guild',
privacy: 'public', privacy: 'private',
leader: user._id, leader: user._id,
}); });
data.groupId = group2._id; data.groupId = group2._id;
await group2.save(); await group2.save();
recipient.guilds.push(group2._id); recipient.guilds.push(group2._id);
await recipient.save(); await recipient.save();
user.guilds.push(group2._id);
await user.save();
await api.createSubscription(data); await api.createSubscription(data);
@@ -757,8 +801,6 @@ describe('Purchasing a group plan for group', () => {
recipient.guilds.push(group._id); recipient.guilds.push(group._id);
await recipient.save(); await recipient.save();
user.guilds.push(group._id);
await user.save();
data.groupId = group._id; data.groupId = group._id;
await api.createSubscription(data); await api.createSubscription(data);
@@ -770,17 +812,22 @@ describe('Purchasing a group plan for group', () => {
const group2 = generateGroup({ const group2 = generateGroup({
name: 'test group2', name: 'test group2',
type: 'guild', type: 'guild',
privacy: 'public', privacy: 'private',
leader: user._id, leader: user._id,
}); });
data.groupId = group2._id; data.groupId = group2._id;
await group2.save(); await group2.save();
recipient.guilds.push(group2._id); recipient.guilds.push(group2._id);
await recipient.save(); await recipient.save();
user.guilds.push(group2._id);
await user.save();
await api.createSubscription(data); await api.createSubscription(data);
const updatedGroup = await Group.findById(group._id).exec(); const updatedGroup = await Group.findById(group._id).exec();
updatedGroup.memberCount = 2;
await updatedGroup.save();
await updatedGroup.leave(recipient); await updatedGroup.leave(recipient);
updatedUser = await User.findById(recipient._id).exec(); updatedUser = await User.findById(recipient._id).exec();
@@ -806,8 +853,6 @@ describe('Purchasing a group plan for group', () => {
recipient.guilds.push(group._id); recipient.guilds.push(group._id);
await recipient.save(); await recipient.save();
user.guilds.push(group._id);
await user.save();
data.groupId = group._id; data.groupId = group._id;
await api.createSubscription(data); await api.createSubscription(data);
@@ -835,8 +880,6 @@ describe('Purchasing a group plan for group', () => {
recipient.guilds.push(group._id); recipient.guilds.push(group._id);
await recipient.save(); await recipient.save();
user.guilds.push(group._id);
await user.save();
data.groupId = group._id; data.groupId = group._id;
await api.createSubscription(data); await api.createSubscription(data);
@@ -864,8 +907,6 @@ describe('Purchasing a group plan for group', () => {
recipient.guilds.push(group._id); recipient.guilds.push(group._id);
await recipient.save(); await recipient.save();
user.guilds.push(group._id);
await user.save();
data.groupId = group._id; data.groupId = group._id;
await api.createSubscription(data); await api.createSubscription(data);
@@ -894,8 +935,6 @@ describe('Purchasing a group plan for group', () => {
recipient.guilds.push(group._id); recipient.guilds.push(group._id);
await recipient.save(); await recipient.save();
user.guilds.push(group._id);
await user.save();
data.groupId = group._id; data.groupId = group._id;
await api.createSubscription(data); await api.createSubscription(data);

View File

@@ -33,11 +33,14 @@ describe('Stripe - Upgrade Group Plan', () => {
group = generateGroup({ group = generateGroup({
name: 'test group', name: 'test group',
type: 'guild', type: 'guild',
privacy: 'public', privacy: 'private',
leader: user._id, leader: user._id,
}); });
await group.save(); await group.save();
user.guilds.push(group._id);
await user.save();
spy = sinon.stub(stripe.subscriptions, 'update'); spy = sinon.stub(stripe.subscriptions, 'update');
spy.resolves([]); spy.resolves([]);
data.groupId = group._id; data.groupId = group._id;

View File

@@ -9,7 +9,7 @@ describe('preenHistory', () => {
beforeEach(() => { beforeEach(() => {
// Replace system clocks so we can get predictable results // Replace system clocks so we can get predictable results
clock = sinon.useFakeTimers({ clock = sinon.useFakeTimers({
now: Number(moment('2013-10-20').zone(0).startOf('day').toDate()), now: Number(moment('2013-10-20').utcOffset(0).startOf('day').toDate()),
toFake: ['Date'], toFake: ['Date'],
}); });
}); });

View File

@@ -1,4 +1,3 @@
import path from 'path'; import path from 'path';
import nconf from 'nconf'; import nconf from 'nconf';
import setupNconf from '../../../../website/server/libs/setupNconf'; import setupNconf from '../../../../website/server/libs/setupNconf';

View File

@@ -21,7 +21,8 @@ describe('cors middleware', () => {
expect(res.set).to.have.been.calledWith({ expect(res.set).to.have.been.calledWith({
'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'OPTIONS,GET,POST,PUT,HEAD,DELETE', 'Access-Control-Allow-Methods': 'OPTIONS,GET,POST,PUT,HEAD,DELETE',
'Access-Control-Allow-Headers': 'Content-Type,Accept,Content-Encoding,X-Requested-With,x-api-user,x-api-key,x-client', 'Access-Control-Allow-Headers': 'Authorization,Content-Type,Accept,Content-Encoding,X-Requested-With,x-api-user,x-api-key,x-client',
'Access-Control-Expose-Headers': 'X-RateLimit-Limit,X-RateLimit-Remaining,X-RateLimit-Reset,Retry-After',
}); });
expect(res.sendStatus).to.not.have.been.called; expect(res.sendStatus).to.not.have.been.called;
expect(next).to.have.been.calledOnce; expect(next).to.have.been.calledOnce;
@@ -33,7 +34,8 @@ describe('cors middleware', () => {
expect(res.set).to.have.been.calledWith({ expect(res.set).to.have.been.calledWith({
'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'OPTIONS,GET,POST,PUT,HEAD,DELETE', 'Access-Control-Allow-Methods': 'OPTIONS,GET,POST,PUT,HEAD,DELETE',
'Access-Control-Allow-Headers': 'Content-Type,Accept,Content-Encoding,X-Requested-With,x-api-user,x-api-key,x-client', 'Access-Control-Allow-Headers': 'Authorization,Content-Type,Accept,Content-Encoding,X-Requested-With,x-api-user,x-api-key,x-client',
'Access-Control-Expose-Headers': 'X-RateLimit-Limit,X-RateLimit-Remaining,X-RateLimit-Reset,Retry-After',
}); });
expect(res.sendStatus).to.have.been.calledWith(200); expect(res.sendStatus).to.have.been.calledWith(200);
expect(next).to.not.have.been.called; expect(next).to.not.have.been.called;

View File

@@ -57,7 +57,7 @@ describe('ipBlocker middleware', () => {
}); });
it('does not throw when the ip does not match', () => { it('does not throw when the ip does not match', () => {
req.headers['x-forwarded-for'] = '192.168.1.1'; req.ip = '192.168.1.1';
sandbox.stub(nconf, 'get').withArgs('BLOCKED_IPS').returns('192.168.1.2'); sandbox.stub(nconf, 'get').withArgs('BLOCKED_IPS').returns('192.168.1.2');
const attachIpBlocker = requireAgain(pathToIpBlocker).default; const attachIpBlocker = requireAgain(pathToIpBlocker).default;
attachIpBlocker(req, res, next); attachIpBlocker(req, res, next);
@@ -65,30 +65,12 @@ describe('ipBlocker middleware', () => {
checkErrorNotThrown(next); checkErrorNotThrown(next);
}); });
it('throws when a matching ip exist in x-forwarded-for', () => { it('throws when the ip is blocked', () => {
req.headers['x-forwarded-for'] = '192.168.1.1'; req.ip = '192.168.1.1';
sandbox.stub(nconf, 'get').withArgs('BLOCKED_IPS').returns('192.168.1.1'); sandbox.stub(nconf, 'get').withArgs('BLOCKED_IPS').returns('192.168.1.1');
const attachIpBlocker = requireAgain(pathToIpBlocker).default; const attachIpBlocker = requireAgain(pathToIpBlocker).default;
attachIpBlocker(req, res, next); attachIpBlocker(req, res, next);
checkErrorThrown(next); checkErrorThrown(next);
}); });
it('trims ips in x-forwarded-for', () => {
req.headers['x-forwarded-for'] = '192.168.1.1';
sandbox.stub(nconf, 'get').withArgs('BLOCKED_IPS').returns(', 192.168.1.1 , 192.168.1.4, ');
const attachIpBlocker = requireAgain(pathToIpBlocker).default;
attachIpBlocker(req, res, next);
checkErrorThrown(next);
});
it('works when multiple ips are passed in x-forwarded-for', () => {
req.headers['x-forwarded-for'] = '192.168.1.4';
sandbox.stub(nconf, 'get').withArgs('BLOCKED_IPS').returns('192.168.1.1, 192.168.1.4, 192.168.1.3');
const attachIpBlocker = requireAgain(pathToIpBlocker).default;
attachIpBlocker(req, res, next);
checkErrorThrown(next);
});
}); });

View File

@@ -0,0 +1,141 @@
import nconf from 'nconf';
import { RateLimiterMemory } from 'rate-limiter-flexible';
import requireAgain from 'require-again';
import {
generateRes,
generateReq,
generateNext,
} from '../../../helpers/api-unit.helper';
import { TooManyRequests } from '../../../../website/server/libs/errors';
import apiError from '../../../../website/server/libs/apiError';
import logger from '../../../../website/server/libs/logger';
describe('rateLimiter middleware', () => {
const pathToRateLimiter = '../../../../website/server/middlewares/rateLimiter';
let res; let req; let next; let nconfGetStub;
beforeEach(() => {
nconfGetStub = sandbox.stub(nconf, 'get');
nconfGetStub.withArgs('NODE_ENV').returns('test');
nconfGetStub.withArgs('IS_TEST').returns(true);
res = generateRes();
req = generateReq();
next = generateNext();
});
afterEach(() => {
sandbox.restore();
});
it('is disabled when the env var is not defined', () => {
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns(undefined);
const attachRateLimiter = requireAgain(pathToRateLimiter).default;
attachRateLimiter(req, res, next);
expect(next).to.have.been.calledOnce;
const calledWith = next.getCall(0).args;
expect(typeof calledWith[0] === 'undefined').to.equal(true);
expect(res.set).to.not.have.been.called;
});
it('is disabled when the env var is an not "true"', () => {
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('false');
const attachRateLimiter = requireAgain(pathToRateLimiter).default;
attachRateLimiter(req, res, next);
expect(next).to.have.been.calledOnce;
const calledWith = next.getCall(0).args;
expect(typeof calledWith[0] === 'undefined').to.equal(true);
expect(res.set).to.not.have.been.called;
});
it('does not throw when there are available points', async () => {
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('true');
const attachRateLimiter = requireAgain(pathToRateLimiter).default;
await attachRateLimiter(req, res, next);
expect(next).to.have.been.calledOnce;
const calledWith = next.getCall(0).args;
expect(typeof calledWith[0] === 'undefined').to.equal(true);
expect(res.set).to.have.been.calledOnce;
expect(res.set).to.have.been.calledWithMatch({
'X-RateLimit-Limit': 30,
'X-RateLimit-Remaining': 29,
'X-RateLimit-Reset': sinon.match(Date),
});
});
it('does not throw when an unknown error is thrown by the rate limiter', async () => {
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('true');
sandbox.stub(logger, 'error');
sandbox.stub(RateLimiterMemory.prototype, 'consume')
.returns(Promise.reject(new Error('Unknown error.')));
const attachRateLimiter = requireAgain(pathToRateLimiter).default;
await attachRateLimiter(req, res, next);
expect(next).to.have.been.calledOnce;
const calledWith = next.getCall(0).args;
expect(typeof calledWith[0] === 'undefined').to.equal(true);
expect(res.set).to.not.have.been.called;
expect(logger.error).to.be.calledOnce;
expect(logger.error).to.have.been.calledWithMatch(Error, 'Rate Limiter Error');
});
it('throws when there are no available points remaining', async () => {
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('true');
const attachRateLimiter = requireAgain(pathToRateLimiter).default;
// call for 31 times
for (let i = 0; i < 31; i += 1) {
await attachRateLimiter(req, res, next); // eslint-disable-line no-await-in-loop
}
expect(next).to.have.been.callCount(31);
const calledWith = next.getCall(30).args;
expect(calledWith[0].message).to.equal(apiError('clientRateLimited'));
expect(calledWith[0] instanceof TooManyRequests).to.equal(true);
expect(res.set).to.have.been.callCount(31);
expect(res.set).to.have.been.calledWithMatch({
'Retry-After': sinon.match(Number),
'X-RateLimit-Limit': 30,
'X-RateLimit-Remaining': 0,
'X-RateLimit-Reset': sinon.match(Date),
});
});
it('uses the user id if supplied or the ip address', async () => {
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('true');
const attachRateLimiter = requireAgain(pathToRateLimiter).default;
req.ip = 1;
await attachRateLimiter(req, res, next);
req.headers['x-api-user'] = 'user-1';
await attachRateLimiter(req, res, next);
await attachRateLimiter(req, res, next);
// user id an ip are counted as separate sources
expect(res.set).to.have.been.calledWithMatch({
'X-RateLimit-Limit': 30,
'X-RateLimit-Remaining': 28, // 2 calls with user id
'X-RateLimit-Reset': sinon.match(Date),
});
req.headers['x-api-user'] = undefined;
await attachRateLimiter(req, res, next);
await attachRateLimiter(req, res, next);
expect(res.set).to.have.been.calledWithMatch({
'X-RateLimit-Limit': 30,
'X-RateLimit-Remaining': 27, // 3 calls with only ip
'X-RateLimit-Reset': sinon.match(Date),
});
});
});

View File

@@ -22,7 +22,7 @@ describe('redirects middleware', () => {
const nconfStub = sandbox.stub(nconf, 'get'); const nconfStub = sandbox.stub(nconf, 'get');
nconfStub.withArgs('BASE_URL').returns('https://habitica.com'); nconfStub.withArgs('BASE_URL').returns('https://habitica.com');
nconfStub.withArgs('IS_PROD').returns(true); nconfStub.withArgs('IS_PROD').returns(true);
req.header = sandbox.stub().withArgs('x-forwarded-proto').returns('http'); req.protocol = 'http';
req.originalUrl = '/static/front'; req.originalUrl = '/static/front';
const attachRedirects = requireAgain(pathToRedirectsMiddleware); const attachRedirects = requireAgain(pathToRedirectsMiddleware);
@@ -37,7 +37,7 @@ describe('redirects middleware', () => {
const nconfStub = sandbox.stub(nconf, 'get'); const nconfStub = sandbox.stub(nconf, 'get');
nconfStub.withArgs('BASE_URL').returns('https://habitica.com'); nconfStub.withArgs('BASE_URL').returns('https://habitica.com');
nconfStub.withArgs('IS_PROD').returns(true); nconfStub.withArgs('IS_PROD').returns(true);
req.header = sandbox.stub().withArgs('x-forwarded-proto').returns('https'); req.protocol = 'https';
req.originalUrl = '/static/front'; req.originalUrl = '/static/front';
const attachRedirects = requireAgain(pathToRedirectsMiddleware); const attachRedirects = requireAgain(pathToRedirectsMiddleware);
@@ -51,7 +51,7 @@ describe('redirects middleware', () => {
const nconfStub = sandbox.stub(nconf, 'get'); const nconfStub = sandbox.stub(nconf, 'get');
nconfStub.withArgs('BASE_URL').returns('https://habitica.com'); nconfStub.withArgs('BASE_URL').returns('https://habitica.com');
nconfStub.withArgs('IS_PROD').returns(false); nconfStub.withArgs('IS_PROD').returns(false);
req.header = sandbox.stub().withArgs('x-forwarded-proto').returns('http'); req.protocol = 'http';
req.originalUrl = '/static/front'; req.originalUrl = '/static/front';
const attachRedirects = requireAgain(pathToRedirectsMiddleware); const attachRedirects = requireAgain(pathToRedirectsMiddleware);
@@ -65,7 +65,7 @@ describe('redirects middleware', () => {
const nconfStub = sandbox.stub(nconf, 'get'); const nconfStub = sandbox.stub(nconf, 'get');
nconfStub.withArgs('BASE_URL').returns('http://habitica.com'); nconfStub.withArgs('BASE_URL').returns('http://habitica.com');
nconfStub.withArgs('IS_PROD').returns(true); nconfStub.withArgs('IS_PROD').returns(true);
req.header = sandbox.stub().withArgs('x-forwarded-proto').returns('http'); req.protocol = 'http';
req.originalUrl = '/static/front'; req.originalUrl = '/static/front';
const attachRedirects = requireAgain(pathToRedirectsMiddleware); const attachRedirects = requireAgain(pathToRedirectsMiddleware);
@@ -81,7 +81,7 @@ describe('redirects middleware', () => {
nconfStub.withArgs('IS_PROD').returns(true); nconfStub.withArgs('IS_PROD').returns(true);
nconfStub.withArgs('SKIP_SSL_CHECK_KEY').returns('test-key'); nconfStub.withArgs('SKIP_SSL_CHECK_KEY').returns('test-key');
req.header = sandbox.stub().withArgs('x-forwarded-proto').returns('http'); req.protocol = 'http';
req.originalUrl = '/static/front'; req.originalUrl = '/static/front';
req.query.skipSSLCheck = 'test-key'; req.query.skipSSLCheck = 'test-key';
@@ -97,7 +97,7 @@ describe('redirects middleware', () => {
nconfStub.withArgs('IS_PROD').returns(true); nconfStub.withArgs('IS_PROD').returns(true);
nconfStub.withArgs('SKIP_SSL_CHECK_KEY').returns('test-key'); nconfStub.withArgs('SKIP_SSL_CHECK_KEY').returns('test-key');
req.header = sandbox.stub().withArgs('x-forwarded-proto').returns('http'); req.protocol = 'http';
req.originalUrl = '/static/front?skipSSLCheck=INVALID'; req.originalUrl = '/static/front?skipSSLCheck=INVALID';
req.query.skipSSLCheck = 'INVALID'; req.query.skipSSLCheck = 'INVALID';
@@ -114,7 +114,7 @@ describe('redirects middleware', () => {
nconfStub.withArgs('IS_PROD').returns(true); nconfStub.withArgs('IS_PROD').returns(true);
nconfStub.withArgs('SKIP_SSL_CHECK_KEY').returns(null); nconfStub.withArgs('SKIP_SSL_CHECK_KEY').returns(null);
req.header = sandbox.stub().withArgs('x-forwarded-proto').returns('http'); req.protocol = 'http';
req.originalUrl = '/static/front'; req.originalUrl = '/static/front';
req.query.skipSSLCheck = 'INVALID'; req.query.skipSSLCheck = 'INVALID';

View File

@@ -16,7 +16,6 @@ describe('response middleware', () => {
next = generateNext(); next = generateNext();
}); });
it('attaches respond method to res', () => { it('attaches respond method to res', () => {
responseMiddleware(req, res, next); responseMiddleware(req, res, next);

View File

@@ -35,6 +35,33 @@ describe('Challenge Model', () => {
notes: 'test notes', notes: 'test notes',
}, },
}; };
const tasks2ToTest = {
habit: {
text: 'test habit 2',
type: 'habit',
up: false,
down: true,
notes: 'test notes',
},
todo: {
text: 'test todo 2',
type: 'todo',
notes: 'test notes',
},
daily: {
text: 'test daily 2',
type: 'daily',
frequency: 'daily',
everyX: 5,
startDate: new Date(),
notes: 'test notes',
},
reward: {
text: 'test reward 2',
type: 'reward',
notes: 'test notes',
},
};
beforeEach(async () => { beforeEach(async () => {
guild = new Group({ guild = new Group({
@@ -146,6 +173,60 @@ describe('Challenge Model', () => {
expect(syncedTask.attribute).to.eql('str'); expect(syncedTask.attribute).to.eql('str');
}); });
it('should add challenge tag back to user upon syncing challenge tasks to a user with challenge tag removed', async () => {
await challenge.addTasks([task]);
const newMember = new User({
guilds: [guild._id],
});
await newMember.save();
await challenge.syncTasksToUser(newMember);
let updatedNewMember = await User.findById(newMember._id).exec();
const updatedNewMemberId = updatedNewMember._id;
updatedNewMember.tags = [];
await updatedNewMember.save();
const taskValue2 = tasks2ToTest[taskType];
const task2 = new Tasks[`${taskType}`](Tasks.Task.sanitize(taskValue2));
task2.challenge.id = challenge._id;
await challenge.addTasks([task2]);
await challenge.syncTasksToUser(updatedNewMember);
updatedNewMember = await User.findById(updatedNewMemberId).exec();
expect(updatedNewMember.tags.length).to.equal(1);
expect(updatedNewMember.tags[0].id).to.equal(challenge._id);
expect(updatedNewMember.tags[0].name).to.equal(challenge.shortName);
});
it('should not add a duplicate challenge tag to user upon syncing challenge tasks to a user with existing challenge tag', async () => {
await challenge.addTasks([task]);
const newMember = new User({
guilds: [guild._id],
});
await newMember.save();
await challenge.syncTasksToUser(newMember);
let updatedNewMember = await User.findById(newMember._id).exec();
const updatedNewMemberId = updatedNewMember._id;
const taskValue2 = tasks2ToTest[taskType];
const task2 = new Tasks[`${taskType}`](Tasks.Task.sanitize(taskValue2));
task2.challenge.id = challenge._id;
await challenge.addTasks([task2]);
await challenge.syncTasksToUser(updatedNewMember);
updatedNewMember = await User.findById(updatedNewMemberId);
expect(updatedNewMember.tags.length).to.equal(8);
expect(updatedNewMember.tags[7].id).to.equal(challenge._id);
expect(updatedNewMember.tags[7].name).to.equal(challenge.shortName);
expect(updatedNewMember.tags.filter(tag => tag.id === challenge._id).length).to.equal(1);
});
it('syncs challenge tasks to a user with the existing task', async () => { it('syncs challenge tasks to a user with the existing task', async () => {
await challenge.addTasks([task]); await challenge.addTasks([task]);

View File

@@ -769,7 +769,6 @@ describe('Group Model', () => {
expect(res.t).to.not.be.called; expect(res.t).to.not.be.called;
}); });
it('does not throw an error if only user ids are passed in', async () => { it('does not throw an error if only user ids are passed in', async () => {
await Group.validateInvitations({ uuids: ['user-id', 'user-id2'] }, res); await Group.validateInvitations({ uuids: ['user-id', 'user-id2'] }, res);
expect(res.t).to.not.be.called; expect(res.t).to.not.be.called;

View File

@@ -235,15 +235,16 @@ describe('Group Task Methods', () => {
}); });
}); });
it('removes an assigned task and unlinks assignees', async () => { it('removes assigned tasks when master task is deleted', async () => {
await guild.syncTask(task, leader); await guild.syncTask(task, leader);
await guild.removeTask(task); await guild.removeTask(task);
const updatedLeader = await User.findOne({ _id: leader._id }); const updatedLeader = await User.findOne({ _id: leader._id });
const updatedLeadersTasks = await Tasks.Task.find({ _id: { $in: updatedLeader.tasksOrder[`${taskType}s`] } }); const updatedLeadersTasks = await Tasks.Task.find({ userId: leader._id, type: taskType });
const syncedTask = find(updatedLeadersTasks, findLinkedTask); const syncedTask = find(updatedLeadersTasks, findLinkedTask);
expect(syncedTask.group.broken).to.equal('TASK_DELETED'); expect(updatedLeader.tasksOrder[`${taskType}s`]).to.not.include(task._id);
expect(syncedTask).to.not.exist;
}); });
it('unlinks and deletes group tasks for a user when remove-all is specified', async () => { it('unlinks and deletes group tasks for a user when remove-all is specified', async () => {

View File

@@ -3,7 +3,6 @@ import { model as Challenge } from '../../../../website/server/models/challenge'
import { model as Group } from '../../../../website/server/models/group'; import { model as Group } from '../../../../website/server/models/group';
import { model as User } from '../../../../website/server/models/user'; import { model as User } from '../../../../website/server/models/user';
import * as Tasks from '../../../../website/server/models/task'; import * as Tasks from '../../../../website/server/models/task';
import { InternalServerError } from '../../../../website/server/libs/errors';
import { generateHistory } from '../../../helpers/api-unit.helper'; import { generateHistory } from '../../../helpers/api-unit.helper';
describe('Task Model', () => { describe('Task Model', () => {
@@ -99,7 +98,8 @@ describe('Task Model', () => {
throw new Error('No exception when Id is None'); throw new Error('No exception when Id is None');
} catch (err) { } catch (err) {
expect(err).to.exist; expect(err).to.exist;
expect(err).to.eql(new InternalServerError('Task identifier is a required argument')); expect(err).to.be.an.instanceOf(Error);
expect(err.message).to.eql('Task identifier is a required argument');
} }
}); });
@@ -109,7 +109,8 @@ describe('Task Model', () => {
throw new Error('No exception when user_id is undefined'); throw new Error('No exception when user_id is undefined');
} catch (err) { } catch (err) {
expect(err).to.exist; expect(err).to.exist;
expect(err).to.eql(new InternalServerError('User identifier is a required argument')); expect(err).to.be.an.instanceOf(Error);
expect(err.message).to.eql('User identifier is a required argument');
} }
}); });
@@ -153,6 +154,132 @@ describe('Task Model', () => {
}); });
}); });
describe('findMultipleByIdOrAlias', () => {
let taskWithAlias;
let secondTask;
let user;
beforeEach(async () => {
user = new User();
await user.save();
taskWithAlias = new Tasks.todo({ // eslint-disable-line new-cap
text: 'some text',
alias: 'short-name',
userId: user.id,
});
await taskWithAlias.save();
secondTask = new Tasks.habit({ // eslint-disable-line new-cap
text: 'second task',
alias: 'second-short-name',
userId: user.id,
});
await secondTask.save();
sandbox.spy(Tasks.Task, 'find');
});
it('throws an error if task identifiers is not passed in', async () => {
try {
await Tasks.Task.findMultipleByIdOrAlias(null, user._id);
throw new Error('No exception when Id is None');
} catch (err) {
expect(err).to.exist;
expect(err).to.be.an.instanceOf(Error);
expect(err.message).to.eql('Task identifiers is a required array argument');
}
});
it('throws an error if task identifiers is not an array', async () => {
try {
await Tasks.Task.findMultipleByIdOrAlias('string', user._id);
throw new Error('No exception when Id is None');
} catch (err) {
expect(err).to.exist;
expect(err).to.be.an.instanceOf(Error);
expect(err.message).to.eql('Task identifiers is a required array argument');
}
});
it('throws an error if user identifier is not passed in', async () => {
try {
await Tasks.Task.findMultipleByIdOrAlias([taskWithAlias._id]);
throw new Error('No exception when user_id is undefined');
} catch (err) {
expect(err).to.exist;
expect(err).to.be.an.instanceOf(Error);
expect(err.message).to.eql('User identifier is a required argument');
}
});
it('returns task by id', async () => {
const foundTasks = await Tasks.Task.findMultipleByIdOrAlias([taskWithAlias._id], user._id);
expect(foundTasks[0].text).to.eql(taskWithAlias.text);
});
it('returns task by alias', async () => {
const foundTasks = await Tasks.Task.findMultipleByIdOrAlias(
[taskWithAlias.alias], user._id,
);
expect(foundTasks[0].text).to.eql(taskWithAlias.text);
});
it('returns multiple tasks', async () => {
const foundTasks = await Tasks.Task.findMultipleByIdOrAlias(
[taskWithAlias.alias, secondTask._id], user._id,
);
expect(foundTasks.length).to.eql(2);
expect(foundTasks[0]._id).to.eql(taskWithAlias._id);
expect(foundTasks[1]._id).to.eql(secondTask._id);
});
it('returns a task only once if searched by both id and alias', async () => {
const foundTasks = await Tasks.Task.findMultipleByIdOrAlias(
[taskWithAlias.alias, taskWithAlias._id], user._id,
);
expect(foundTasks.length).to.eql(1);
expect(foundTasks[0].text).to.eql(taskWithAlias.text);
});
it('scopes alias lookup to user', async () => {
await Tasks.Task.findMultipleByIdOrAlias([taskWithAlias.alias], user._id);
expect(Tasks.Task.find).to.be.calledOnce;
expect(Tasks.Task.find).to.be.calledWithMatch({
$or: [
{ _id: { $in: [] } },
{ alias: { $in: [taskWithAlias.alias] } },
],
userId: user._id,
});
});
it('returns empty array if tasks cannot be found', async () => {
const foundTasks = await Tasks.Task.findMultipleByIdOrAlias(['not-found'], user._id);
expect(foundTasks).to.eql([]);
});
it('accepts additional query parameters', async () => {
await Tasks.Task.findMultipleByIdOrAlias([taskWithAlias.alias], user._id, { foo: 'bar' });
expect(Tasks.Task.find).to.be.calledOnce;
expect(Tasks.Task.find).to.be.calledWithMatch({
$or: [
{ _id: { $in: [] } },
{ alias: { $in: [taskWithAlias.alias] } },
],
userId: user._id,
foo: 'bar',
});
});
});
describe('sanitizeUserChallengeTask ', () => { describe('sanitizeUserChallengeTask ', () => {
}); });

View File

@@ -432,7 +432,6 @@ describe('User Model', () => {
user = new User(); user = new User();
}); });
it('returns false if user does not have customer id', () => { it('returns false if user does not have customer id', () => {
expect(user.isSubscribed()).to.be.undefined; expect(user.isSubscribed()).to.be.undefined;
}); });
@@ -558,7 +557,6 @@ describe('User Model', () => {
}); });
}); });
context('hasCancelled', () => { context('hasCancelled', () => {
let user; let user;
beforeEach(() => { beforeEach(() => {
@@ -763,7 +761,7 @@ describe('User Model', () => {
}); });
}); });
context('days missed', () => { describe('daysUserHasMissed', () => {
// http://forbrains.co.uk/international_tools/earth_timezones // http://forbrains.co.uk/international_tools/earth_timezones
let user; let user;
@@ -771,24 +769,51 @@ describe('User Model', () => {
user = new User(); user = new User();
}); });
it('should not cron early when going back a timezone', () => { it('correctly calculates days missed since lastCron', () => {
const yesterday = moment('2017-12-05T00:00:00.000-06:00'); // 11 pm on 4 Texas const now = moment();
const timezoneOffset = moment().zone('-06:00').zone(); user.lastCron = moment(now).subtract(5, 'days');
user.lastCron = yesterday;
user.preferences.timezoneOffset = timezoneOffset;
const today = moment('2017-12-06T00:00:00.000-06:00'); // 11 pm on 4 Texas const { daysMissed } = user.daysUserHasMissed(now);
const req = {};
req.header = () => timezoneOffset + 60;
const { daysMissed } = user.daysUserHasMissed(today, req); expect(daysMissed).to.eql(5);
});
it('uses timezone from preferences to calculate days missed', () => {
const now = moment('2017-07-08 01:00:00Z');
user.lastCron = moment('2017-07-04 13:00:00Z');
user.preferences.timezoneOffset = 120;
const { daysMissed } = user.daysUserHasMissed(now);
expect(daysMissed).to.eql(3);
});
it('uses timezone at last cron to calculate days missed', () => {
const now = moment('2017-09-08 13:00:00Z');
user.lastCron = moment('2017-09-06 01:00:00+02:00');
user.preferences.timezoneOffset = 0;
user.preferences.timezoneOffsetAtLastCron = -120;
const { daysMissed } = user.daysUserHasMissed(now);
expect(daysMissed).to.eql(2);
});
it('respects new timezone that drags time into same day', () => {
user.lastCron = moment('2017-12-05T00:00:00.000-06:00');
user.preferences.timezoneOffset = 360;
const today = moment('2017-12-06T00:00:00.000-06:00');
const requestWithMinus7Timezone = { header: () => 420 };
const { daysMissed } = user.daysUserHasMissed(today, requestWithMinus7Timezone);
expect(user.preferences.timezoneOffset).to.eql(420);
expect(daysMissed).to.eql(0); expect(daysMissed).to.eql(0);
}); });
it('should not cron early when going back a timezone with a custom day start', () => { it('should not cron early when going back a timezone with a custom day start', () => {
const yesterday = moment('2017-12-05T02:00:00.000-08:00'); const yesterday = moment('2017-12-05T02:00:00.000-08:00');
const timezoneOffset = moment().zone('-08:00').zone(); const timezoneOffset = 480;
user.lastCron = yesterday; user.lastCron = yesterday;
user.preferences.timezoneOffset = timezoneOffset; user.preferences.timezoneOffset = timezoneOffset;
user.preferences.dayStart = 2; user.preferences.dayStart = 2;

View File

@@ -299,7 +299,6 @@ describe('Webhook Model', () => {
}); });
}); });
context('type is globalActivity', () => { context('type is globalActivity', () => {
let config; let config;

View File

@@ -117,7 +117,7 @@ describe('GET /challenges/:challengeId/members', () => {
expect(res[0].profile).to.have.all.keys(['name']); expect(res[0].profile).to.have.all.keys(['name']);
}); });
it('returns only first 30 members if req.query.includeAllMembers is not true', async () => { it('returns only first 30 members if req.query.includeAllMembers is not true and req.query.limit is undefined', async () => {
const group = await generateGroup(user, { type: 'party', name: generateUUID() }); const group = await generateGroup(user, { type: 'party', name: generateUUID() });
const challenge = await generateChallenge(user, group); const challenge = await generateChallenge(user, group);
await user.post(`/challenges/${challenge._id}/join`); await user.post(`/challenges/${challenge._id}/join`);
@@ -136,7 +136,7 @@ describe('GET /challenges/:challengeId/members', () => {
}); });
}); });
it('returns only first 30 members if req.query.includeAllMembers is not defined', async () => { it('returns only first 30 members if req.query.includeAllMembers is not defined and req.query.limit is undefined', async () => {
const group = await generateGroup(user, { type: 'party', name: generateUUID() }); const group = await generateGroup(user, { type: 'party', name: generateUUID() });
const challenge = await generateChallenge(user, group); const challenge = await generateChallenge(user, group);
await user.post(`/challenges/${challenge._id}/join`); await user.post(`/challenges/${challenge._id}/join`);
@@ -155,6 +155,68 @@ describe('GET /challenges/:challengeId/members', () => {
}); });
}); });
it('returns an error if req.query.limit is over 60', async () => {
const group = await generateGroup(user, { type: 'party', privacy: 'private' });
const challenge = await generateChallenge(user, group);
const anotherUser = await generateUser();
await expect(anotherUser.get(`/challenges/${challenge._id}/members?limit=61`)).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('invalidReqParams'),
});
});
it('returns an error if req.query.limit is under 1', async () => {
const group = await generateGroup(user, { type: 'party', privacy: 'private' });
const challenge = await generateChallenge(user, group);
const anotherUser = await generateUser();
await expect(anotherUser.get(`/challenges/${challenge._id}/members?limit=-13`)).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('invalidReqParams'),
});
});
it('returns an error if req.query.limit is not an integer', async () => {
const group = await generateGroup(user, { type: 'party', privacy: 'private' });
const challenge = await generateChallenge(user, group);
const anotherUser = await generateUser();
await expect(anotherUser.get(`/challenges/${challenge._id}/members?limit=true`)).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('invalidReqParams'),
});
});
it('returns up to 60 members when req.query.limit is specified', async () => {
const group = await generateGroup(user, { type: 'party', name: generateUUID() });
const challenge = await generateChallenge(user, group);
await user.post(`/challenges/${challenge._id}/join`);
const usersToGenerate = [];
for (let i = 0; i < 62; i += 1) {
usersToGenerate.push(generateUser({ challenges: [challenge._id] }));
}
await Promise.all(usersToGenerate);
let res = await user.get(`/challenges/${challenge._id}/members?limit=57`);
expect(res.length).to.equal(57);
res.forEach(member => {
expect(member).to.have.all.keys(['_id', 'auth', 'flags', 'id', 'profile']);
expect(member.profile).to.have.all.keys(['name']);
});
res = await user.get(`/challenges/${challenge._id}/members?limit=60&lastId=${res[res.length - 1]._id}`);
expect(res.length).to.equal(6);
res.forEach(member => {
expect(member).to.have.all.keys(['_id', 'auth', 'flags', 'id', 'profile']);
expect(member.profile).to.have.all.keys(['name']);
});
}).timeout(30000);
it('returns all members if req.query.includeAllMembers is true', async () => { it('returns all members if req.query.includeAllMembers is true', async () => {
const group = await generateGroup(user, { type: 'party', name: generateUUID() }); const group = await generateGroup(user, { type: 'party', name: generateUUID() });
const challenge = await generateChallenge(user, group); const challenge = await generateChallenge(user, group);

View File

@@ -6,8 +6,8 @@ import {
describe('GET challenges/user', () => { describe('GET challenges/user', () => {
context('no official challenges', () => { context('no official challenges', () => {
let user; let member; let nonMember; let challenge; let challenge2; let let user; let member; let nonMember; let challenge; let challenge2;
publicGuild; let publicGuild; let userData; let groupData;
before(async () => { before(async () => {
const { group, groupLeader, members } = await createAndPopulateGroup({ const { group, groupLeader, members } = await createAndPopulateGroup({
@@ -19,225 +19,197 @@ describe('GET challenges/user', () => {
members: 1, members: 1,
}); });
user = groupLeader;
publicGuild = group; publicGuild = group;
groupData = {
_id: publicGuild._id,
categories: [],
id: publicGuild._id,
type: publicGuild.type,
privacy: publicGuild.privacy,
name: publicGuild.name,
summary: publicGuild.name,
leader: publicGuild.leader._id,
};
user = groupLeader;
userData = {
_id: publicGuild.leader._id,
id: publicGuild.leader._id,
profile: { name: user.profile.name },
auth: {
local: {
username: user.auth.local.username,
},
},
flags: {
verifiedUsername: true,
},
};
member = members[0]; // eslint-disable-line prefer-destructuring member = members[0]; // eslint-disable-line prefer-destructuring
nonMember = await generateUser(); nonMember = await generateUser();
challenge = await generateChallenge(user, group); challenge = await generateChallenge(user, group);
await user.post(`/challenges/${challenge._id}/join`);
challenge2 = await generateChallenge(user, group); challenge2 = await generateChallenge(user, group);
await user.post(`/challenges/${challenge2._id}/join`);
});
it('should return challenges user has joined', async () => {
await nonMember.post(`/challenges/${challenge._id}/join`); await nonMember.post(`/challenges/${challenge._id}/join`);
});
context('all challenges', () => {
it('should return challenges user has joined', async () => {
const challenges = await nonMember.get('/challenges/user');
const challenges = await nonMember.get('/challenges/user'); const foundChallenge = _.find(challenges, { _id: challenge._id });
expect(foundChallenge).to.exist;
expect(foundChallenge.leader).to.eql(userData);
expect(foundChallenge.group).to.eql(groupData);
});
const foundChallenge = _.find(challenges, { _id: challenge._id }); it('should not return challenges a non-member has not joined', async () => {
expect(foundChallenge).to.exist; const challenges = await nonMember.get('/challenges/user');
expect(foundChallenge.leader).to.eql({
_id: publicGuild.leader._id, const foundChallenge2 = _.find(challenges, { _id: challenge2._id });
id: publicGuild.leader._id, expect(foundChallenge2).to.not.exist;
profile: { name: user.profile.name }, });
auth: {
local: { it('should return challenges user has created', async () => {
username: user.auth.local.username, const challenges = await user.get('/challenges/user');
const foundChallenge1 = _.find(challenges, { _id: challenge._id });
expect(foundChallenge1).to.exist;
expect(foundChallenge1.leader).to.eql(userData);
expect(foundChallenge1.group).to.eql(groupData);
const foundChallenge2 = _.find(challenges, { _id: challenge2._id });
expect(foundChallenge2).to.exist;
expect(foundChallenge2.leader).to.eql(userData);
expect(foundChallenge2.group).to.eql(groupData);
});
it('should return challenges in user\'s group', async () => {
const challenges = await member.get('/challenges/user');
const foundChallenge1 = _.find(challenges, { _id: challenge._id });
expect(foundChallenge1).to.exist;
expect(foundChallenge1.leader).to.eql(userData);
expect(foundChallenge1.group).to.eql(groupData);
const foundChallenge2 = _.find(challenges, { _id: challenge2._id });
expect(foundChallenge2).to.exist;
expect(foundChallenge2.leader).to.eql(userData);
expect(foundChallenge2.group).to.eql(groupData);
});
it('should return newest challenges first', async () => {
let challenges = await user.get('/challenges/user');
let foundChallengeIndex = _.findIndex(challenges, { _id: challenge2._id });
expect(foundChallengeIndex).to.eql(0);
const newChallenge = await generateChallenge(user, publicGuild);
await user.post(`/challenges/${newChallenge._id}/join`);
challenges = await user.get('/challenges/user');
foundChallengeIndex = _.findIndex(challenges, { _id: newChallenge._id });
expect(foundChallengeIndex).to.eql(0);
});
it('should not return challenges user doesn\'t have access to', async () => {
const { group, groupLeader } = await createAndPopulateGroup({
groupDetails: {
name: 'TestPrivateGuild',
summary: 'summary for TestPrivateGuild',
type: 'guild',
privacy: 'private',
}, },
}, });
flags: {
verifiedUsername: true,
},
});
expect(foundChallenge.group).to.eql({
_id: publicGuild._id,
categories: [],
id: publicGuild._id,
type: publicGuild.type,
privacy: publicGuild.privacy,
name: publicGuild.name,
summary: publicGuild.name,
leader: publicGuild.leader._id,
});
});
it('should return challenges user has created', async () => { const privateChallenge = await generateChallenge(groupLeader, group);
const challenges = await user.get('/challenges/user'); await groupLeader.post(`/challenges/${privateChallenge._id}/join`);
const foundChallenge1 = _.find(challenges, { _id: challenge._id }); const challenges = await nonMember.get('/challenges/user');
expect(foundChallenge1).to.exist;
expect(foundChallenge1.leader).to.eql({ const foundChallenge = _.find(challenges, { _id: privateChallenge._id });
_id: publicGuild.leader._id, expect(foundChallenge).to.not.exist;
id: publicGuild.leader._id, });
profile: { name: user.profile.name },
auth: { it('should not return challenges user doesn\'t have access to, even with query parameters', async () => {
local: { const { group, groupLeader } = await createAndPopulateGroup({
username: user.auth.local.username, groupDetails: {
name: 'TestPrivateGuild',
summary: 'summary for TestPrivateGuild',
type: 'guild',
privacy: 'private',
}, },
}, });
flags: {
verifiedUsername: true, const privateChallenge = await generateChallenge(groupLeader, group, {
}, categories: [{
}); name: 'academics',
expect(foundChallenge1.group).to.eql({ slug: 'academics',
_id: publicGuild._id, }],
categories: [], });
id: publicGuild._id, await groupLeader.post(`/challenges/${privateChallenge._id}/join`);
type: publicGuild.type,
privacy: publicGuild.privacy, const challenges = await nonMember.get('/challenges/user?categories=academics&owned=not_owned');
name: publicGuild.name,
summary: publicGuild.name, const foundChallenge = _.find(challenges, { _id: privateChallenge._id });
leader: publicGuild.leader._id, expect(foundChallenge).to.not.exist;
});
const foundChallenge2 = _.find(challenges, { _id: challenge2._id });
expect(foundChallenge2).to.exist;
expect(foundChallenge2.leader).to.eql({
_id: publicGuild.leader._id,
id: publicGuild.leader._id,
profile: { name: user.profile.name },
auth: {
local: {
username: user.auth.local.username,
},
},
flags: {
verifiedUsername: true,
},
});
expect(foundChallenge2.group).to.eql({
_id: publicGuild._id,
categories: [],
id: publicGuild._id,
type: publicGuild.type,
privacy: publicGuild.privacy,
name: publicGuild.name,
summary: publicGuild.name,
leader: publicGuild.leader._id,
}); });
}); });
it('should return challenges in user\'s group', async () => { context('my challenges', () => {
const challenges = await member.get('/challenges/user'); it('should return challenges user has joined', async () => {
const challenges = await nonMember.get(`/challenges/user?member=${true}`);
const foundChallenge1 = _.find(challenges, { _id: challenge._id }); const foundChallenge = _.find(challenges, { _id: challenge._id });
expect(foundChallenge1).to.exist; expect(foundChallenge).to.exist;
expect(foundChallenge1.leader).to.eql({ expect(foundChallenge.leader).to.eql(userData);
_id: publicGuild.leader._id, expect(foundChallenge.group).to.eql(groupData);
id: publicGuild.leader._id,
profile: { name: user.profile.name },
auth: {
local: {
username: user.auth.local.username,
},
},
flags: {
verifiedUsername: true,
},
});
expect(foundChallenge1.group).to.eql({
_id: publicGuild._id,
categories: [],
id: publicGuild._id,
type: publicGuild.type,
privacy: publicGuild.privacy,
name: publicGuild.name,
summary: publicGuild.name,
leader: publicGuild.leader._id,
});
const foundChallenge2 = _.find(challenges, { _id: challenge2._id });
expect(foundChallenge2).to.exist;
expect(foundChallenge2.leader).to.eql({
_id: publicGuild.leader._id,
id: publicGuild.leader._id,
profile: { name: user.profile.name },
auth: {
local: {
username: user.auth.local.username,
},
},
flags: {
verifiedUsername: true,
},
});
expect(foundChallenge2.group).to.eql({
_id: publicGuild._id,
categories: [],
id: publicGuild._id,
type: publicGuild.type,
privacy: publicGuild.privacy,
name: publicGuild.name,
summary: publicGuild.name,
leader: publicGuild.leader._id,
});
});
it('should not return challenges in user groups if we send member true param', async () => {
const challenges = await member.get(`/challenges/user?member=${true}`);
const foundChallenge1 = _.find(challenges, { _id: challenge._id });
expect(foundChallenge1).to.not.exist;
const foundChallenge2 = _.find(challenges, { _id: challenge2._id });
expect(foundChallenge2).to.not.exist;
});
it('should return newest challenges first', async () => {
let challenges = await user.get('/challenges/user');
let foundChallengeIndex = _.findIndex(challenges, { _id: challenge2._id });
expect(foundChallengeIndex).to.eql(0);
const newChallenge = await generateChallenge(user, publicGuild);
await user.post(`/challenges/${newChallenge._id}/join`);
challenges = await user.get('/challenges/user');
foundChallengeIndex = _.findIndex(challenges, { _id: newChallenge._id });
expect(foundChallengeIndex).to.eql(0);
});
it('should not return challenges user doesn\'t have access to', async () => {
const { group, groupLeader } = await createAndPopulateGroup({
groupDetails: {
name: 'TestPrivateGuild',
summary: 'summary for TestPrivateGuild',
type: 'guild',
privacy: 'private',
},
}); });
const privateChallenge = await generateChallenge(groupLeader, group); it('should return challenges user has created', async () => {
await groupLeader.post(`/challenges/${privateChallenge._id}/join`); const challenges = await user.get(`/challenges/user?member=${true}`);
const challenges = await nonMember.get('/challenges/user'); const foundChallenge1 = _.find(challenges, { _id: challenge._id });
expect(foundChallenge1).to.exist;
const foundChallenge = _.find(challenges, { _id: privateChallenge._id }); expect(foundChallenge1.leader).to.eql(userData);
expect(foundChallenge).to.not.exist; expect(foundChallenge1.group).to.eql(groupData);
}); const foundChallenge2 = _.find(challenges, { _id: challenge2._id });
expect(foundChallenge2).to.exist;
it('should not return challenges user doesn\'t have access to, even with query parameters', async () => { expect(foundChallenge2.leader).to.eql(userData);
const { group, groupLeader } = await createAndPopulateGroup({ expect(foundChallenge2.group).to.eql(groupData);
groupDetails: {
name: 'TestPrivateGuild',
summary: 'summary for TestPrivateGuild',
type: 'guild',
privacy: 'private',
},
}); });
const privateChallenge = await generateChallenge(groupLeader, group, { it('should return challenges user has created if filter by owned', async () => {
categories: [{ const challenges = await user.get(`/challenges/user?member=${true}&owned=owned`);
name: 'academics',
slug: 'academics', const foundChallenge1 = _.find(challenges, { _id: challenge._id });
}], expect(foundChallenge1).to.exist;
expect(foundChallenge1.leader).to.eql(userData);
expect(foundChallenge1.group).to.eql(groupData);
const foundChallenge2 = _.find(challenges, { _id: challenge2._id });
expect(foundChallenge2).to.exist;
expect(foundChallenge2.leader).to.eql(userData);
expect(foundChallenge2.group).to.eql(groupData);
}); });
await groupLeader.post(`/challenges/${privateChallenge._id}/join`);
const challenges = await nonMember.get('/challenges/user?categories=academics&owned=not_owned'); it('should not return challenges user has created if filter by not owned', async () => {
const challenges = await user.get(`/challenges/user?owned=not_owned&member=${true}`);
const foundChallenge = _.find(challenges, { _id: privateChallenge._id }); const foundChallenge1 = _.find(challenges, { _id: challenge._id });
expect(foundChallenge).to.not.exist; expect(foundChallenge1).to.not.exist;
const foundChallenge2 = _.find(challenges, { _id: challenge2._id });
expect(foundChallenge2).to.not.exist;
});
it('should not return challenges in user groups', async () => {
const challenges = await member.get(`/challenges/user?member=${true}`);
const foundChallenge1 = _.find(challenges, { _id: challenge._id });
expect(foundChallenge1).to.not.exist;
const foundChallenge2 = _.find(challenges, { _id: challenge2._id });
expect(foundChallenge2).to.not.exist;
});
}); });
}); });

View File

@@ -159,7 +159,7 @@ describe('POST /challenges/:challengeId/winner/:winnerId', () => {
expect(testTask.challenge.broken).to.eql('CHALLENGE_CLOSED'); expect(testTask.challenge.broken).to.eql('CHALLENGE_CLOSED');
expect(testTask.challenge.winner).to.eql(winningUser.profile.name); expect(testTask.challenge.winner).to.eql(winningUser.profile.name);
expect(challengeTag.challenge).to.eql('false'); expect(challengeTag.challenge).to.eql(false);
}); });
}); });
}); });

View File

@@ -12,7 +12,7 @@ import apiError from '../../../../../website/server/libs/apiError';
describe('GET /groups', () => { describe('GET /groups', () => {
let user; let user;
let userInGuild; let userInGuild;
const NUMBER_OF_PUBLIC_GUILDS = 3; // 2 + the tavern const NUMBER_OF_PUBLIC_GUILDS = 2;
const NUMBER_OF_PUBLIC_GUILDS_USER_IS_LEADER = 2; const NUMBER_OF_PUBLIC_GUILDS_USER_IS_LEADER = 2;
const NUMBER_OF_PUBLIC_GUILDS_USER_IS_MEMBER = 1; const NUMBER_OF_PUBLIC_GUILDS_USER_IS_MEMBER = 1;
const NUMBER_OF_USERS_PRIVATE_GUILDS = 1; const NUMBER_OF_USERS_PRIVATE_GUILDS = 1;
@@ -236,11 +236,22 @@ describe('GET /groups', () => {
await expect(user.get('/groups?type=publicGuilds&paginate=true&page=1')) await expect(user.get('/groups?type=publicGuilds&paginate=true&page=1'))
.to.eventually.have.a.lengthOf(GUILD_PER_PAGE); .to.eventually.have.a.lengthOf(GUILD_PER_PAGE);
const page2 = await expect(user.get('/groups?type=publicGuilds&paginate=true&page=2')) const page2 = await expect(user.get('/groups?type=publicGuilds&paginate=true&page=2'))
.to.eventually.have.a.lengthOf(1 + 4); // 1 created now, 4 by other tests // 1 created now, 4 by other tests, -1 for no more tavern.
expect(page2[4].name).to.equal('guild with less members'); .to.eventually.have.a.lengthOf(1 + 4 - 1);
expect(page2[3].name).to.equal('guild with less members');
}).timeout(10000); }).timeout(10000);
}); });
it('makes sure that the tavern doesn\'t show up when guilds is passed as a query', async () => {
const guilds = await user.get('/groups?type=guilds');
expect(guilds.find(g => g.id === TAVERN_ID)).to.be.undefined;
});
it('makes sure that the tavern doesn\'t show up when publicGuilds is passed as a query', async () => {
const guilds = await user.get('/groups?type=publicGuilds');
expect(guilds.find(g => g.id === TAVERN_ID)).to.be.undefined;
});
it('returns all the user\'s guilds when guilds passed in as query', async () => { it('returns all the user\'s guilds when guilds passed in as query', async () => {
await expect(user.get('/groups?type=guilds')) await expect(user.get('/groups?type=guilds'))
.to.eventually.have.a .to.eventually.have.a
@@ -254,7 +265,7 @@ describe('GET /groups', () => {
it('returns a list of groups user has access to', async () => { it('returns a list of groups user has access to', async () => {
await expect(user.get('/groups?type=privateGuilds,publicGuilds,party,tavern')) await expect(user.get('/groups?type=privateGuilds,publicGuilds,party,tavern'))
.to.eventually.have.lengthOf(NUMBER_OF_GROUPS_USER_CAN_VIEW); .to.eventually.have.lengthOf(NUMBER_OF_GROUPS_USER_CAN_VIEW - 1); // -1 for no Tavern.
}); });
it('returns a list of groups user has access to', async () => { it('returns a list of groups user has access to', async () => {

View File

@@ -70,7 +70,7 @@ describe('GET /groups/:groupId/invites', () => {
expect(res[0].profile).to.have.all.keys(['name']); expect(res[0].profile).to.have.all.keys(['name']);
}); });
it('returns only first 30 invites', async () => { it('returns only first 30 invites by default (req.query.limit not specified)', async () => {
const leader = await generateUser({ balance: 4 }); const leader = await generateUser({ balance: 4 });
const group = await generateGroup(leader, { type: 'guild', privacy: 'public', name: generateUUID() }); const group = await generateGroup(leader, { type: 'guild', privacy: 'public', name: generateUUID() });
@@ -89,6 +89,65 @@ describe('GET /groups/:groupId/invites', () => {
}); });
}).timeout(10000); }).timeout(10000);
it('returns an error if req.query.limit is over 60', async () => {
const leader = await generateUser({ balance: 4 });
const group = await generateGroup(leader, { type: 'guild', privacy: 'public', name: generateUUID() });
await expect(leader.get(`/groups/${group._id}/invites?limit=61`)).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('invalidReqParams'),
});
});
it('returns an error if req.query.limit is under 1', async () => {
const leader = await generateUser({ balance: 4 });
const group = await generateGroup(leader, { type: 'guild', privacy: 'public', name: generateUUID() });
await expect(leader.get(`/groups/${group._id}/invites?limit=-1`)).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('invalidReqParams'),
});
});
it('returns an error if req.query.limit is not an integer', async () => {
const leader = await generateUser({ balance: 4 });
const group = await generateGroup(leader, { type: 'guild', privacy: 'public', name: generateUUID() });
await expect(leader.get(`/groups/${group._id}/invites?limit=1.3`)).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('invalidReqParams'),
});
});
it('returns up to 60 invites when req.query.limit is specified', async () => {
const leader = await generateUser({ balance: 4 });
const group = await generateGroup(leader, { type: 'guild', privacy: 'public', name: generateUUID() });
const invitesToGenerate = [];
for (let i = 0; i < 31; i += 1) {
invitesToGenerate.push(generateUser());
}
const generatedInvites = await Promise.all(invitesToGenerate);
await leader.post(`/groups/${group._id}/invite`, { uuids: generatedInvites.map(invite => invite._id) });
let res = await leader.get(`/groups/${group._id}/invites?limit=14`);
expect(res.length).to.equal(14);
res.forEach(member => {
expect(member).to.have.all.keys(['_id', 'auth', 'flags', 'id', 'profile']);
expect(member.profile).to.have.all.keys(['name']);
});
res = await leader.get(`/groups/${group._id}/invites?limit=31`);
expect(res.length).to.equal(31);
res.forEach(member => {
expect(member).to.have.all.keys(['_id', 'auth', 'flags', 'id', 'profile']);
expect(member.profile).to.have.all.keys(['name']);
});
}).timeout(30000);
it('supports using req.query.lastId to get more invites', async function test () { it('supports using req.query.lastId to get more invites', async function test () {
this.timeout(30000); // @TODO: times out after 8 seconds this.timeout(30000); // @TODO: times out after 8 seconds
const leader = await generateUser({ balance: 4 }); const leader = await generateUser({ balance: 4 });

View File

@@ -116,7 +116,7 @@ describe('GET /groups/:groupId/members', () => {
expect(memberRes.inbox.messages).to.not.exist; expect(memberRes.inbox.messages).to.not.exist;
}); });
it('returns only first 30 members', async () => { it('returns only first 30 members by default (req.query.limit not specified)', async () => {
const group = await generateGroup(user, { type: 'party', name: generateUUID() }); const group = await generateGroup(user, { type: 'party', name: generateUUID() });
const usersToGenerate = []; const usersToGenerate = [];
@@ -133,6 +133,60 @@ describe('GET /groups/:groupId/members', () => {
}); });
}); });
it('returns an error if req.query.limit is over 60', async () => {
await generateGroup(user, { type: 'party', name: generateUUID() });
await expect(user.get('/groups/party/members?limit=61')).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('invalidReqParams'),
});
});
it('returns an error if req.query.limit is under 1', async () => {
await generateGroup(user, { type: 'party', name: generateUUID() });
await expect(user.get('/groups/party/members?limit=0')).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('invalidReqParams'),
});
});
it('returns an error if req.query.limit is not an integer', async () => {
await generateGroup(user, { type: 'party', name: generateUUID() });
await expect(user.get('/groups/party/members?limit=1.1')).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('invalidReqParams'),
});
});
it('returns up to 60 members when req.query.limit is specified', async () => {
const group = await generateGroup(user, { type: 'party', name: generateUUID() });
const usersToGenerate = [];
for (let i = 0; i < 62; i += 1) {
usersToGenerate.push(generateUser({ party: { _id: group._id } }));
}
await Promise.all(usersToGenerate);
let res = await user.get('/groups/party/members?limit=60');
expect(res.length).to.equal(60);
res.forEach(member => {
expect(member).to.have.all.keys(['_id', 'auth', 'flags', 'id', 'profile']);
expect(member.profile).to.have.all.keys(['name']);
});
res = await user.get(`/groups/party/members?limit=60&lastId=${res[res.length - 1]._id}`);
expect(res.length).to.equal(3);
res.forEach(member => {
expect(member).to.have.all.keys(['_id', 'auth', 'flags', 'id', 'profile']);
expect(member.profile).to.have.all.keys(['name']);
});
}).timeout(30000);
it('returns only first 30 members even when ?includeAllMembers=true', async () => { it('returns only first 30 members even when ?includeAllMembers=true', async () => {
const group = await generateGroup(user, { type: 'party', name: generateUUID() }); const group = await generateGroup(user, { type: 'party', name: generateUUID() });

View File

@@ -75,12 +75,7 @@ describe('POST /group/:groupId/remove-manager', () => {
await nonLeader.post(`/tasks/${task._id}/assign/${nonManager._id}`); await nonLeader.post(`/tasks/${task._id}/assign/${nonManager._id}`);
const memberTasks = await nonManager.get('/tasks/user'); const memberTasks = await nonManager.get('/tasks/user');
const syncedTask = find(memberTasks, findAssignedTask); const syncedTask = find(memberTasks, findAssignedTask);
await expect(nonManager.post(`/tasks/${syncedTask._id}/score/up`)) await nonManager.post(`/tasks/${syncedTask._id}/score/up`);
.to.eventually.be.rejected.and.to.eql({
code: 401,
error: 'NotAuthorized',
message: t('taskApprovalHasBeenRequested'),
});
const updatedGroup = await leader.post(`/groups/${groupToUpdate._id}/remove-manager`, { const updatedGroup = await leader.post(`/groups/${groupToUpdate._id}/remove-manager`, {
managerId: nonLeader._id, managerId: nonLeader._id,

View File

@@ -203,6 +203,16 @@ describe('POST /group/:groupId/join', () => {
await expect(invitedUser.get('/user')).to.eventually.have.nested.property('party._id', party._id); await expect(invitedUser.get('/user')).to.eventually.have.nested.property('party._id', party._id);
}); });
it('Issue #12291: accepting a redundant party invite will let the user stay in the party', async () => {
await invitedUser.update({
'party._id': party._id,
});
await expect(invitedUser.get('/user')).to.eventually.have.nested.property('party._id', party._id);
await invitedUser.post(`/groups/${party._id}/join`);
await expect(invitedUser.get('/user')).to.eventually.have.nested.property('party._id', party._id);
});
it('notifies inviting user that their invitation was accepted', async () => { it('notifies inviting user that their invitation was accepted', async () => {
await invitedUser.post(`/groups/${party._id}/join`); await invitedUser.post(`/groups/${party._id}/join`);

View File

@@ -274,6 +274,7 @@ describe('POST /groups/:groupId/leave', () => {
each(typesOfGroups, (groupDetails, groupType) => { each(typesOfGroups, (groupDetails, groupType) => {
context(`Leaving a group plan when the group is a ${groupType}`, () => { context(`Leaving a group plan when the group is a ${groupType}`, () => {
if (groupDetails.privacy === 'public') return; // public guilds cannot be group plans
let groupWithPlan; let groupWithPlan;
let leader; let leader;
let member; let member;
@@ -341,6 +342,7 @@ describe('POST /groups/:groupId/leave', () => {
each(typesOfGroups, (groupDetails, groupType) => { each(typesOfGroups, (groupDetails, groupType) => {
context(`Leaving a group with extraMonths left plan when the group is a ${groupType}`, () => { context(`Leaving a group with extraMonths left plan when the group is a ${groupType}`, () => {
if (groupDetails.privacy === 'public') return; // public guilds cannot be group plans
const extraMonths = 12; const extraMonths = 12;
let groupWithPlan; let groupWithPlan;
let member; let member;

View File

@@ -32,7 +32,7 @@ describe('payments - stripe - #checkout', () => {
stripePayments.checkout.restore(); stripePayments.checkout.restore();
}); });
it('cancels a user subscription', async () => { it('creates a user subscription', async () => {
user = await generateUser({ user = await generateUser({
'profile.name': 'sender', 'profile.name': 'sender',
'purchased.plan.customerId': 'customer-id', 'purchased.plan.customerId': 'customer-id',
@@ -48,7 +48,7 @@ describe('payments - stripe - #checkout', () => {
expect(stripeCheckoutSubscriptionStub.args[0][0].groupId).to.eql(undefined); expect(stripeCheckoutSubscriptionStub.args[0][0].groupId).to.eql(undefined);
}); });
it('cancels a group subscription', async () => { it('creates a group subscription', async () => {
user = await generateUser({ user = await generateUser({
'profile.name': 'sender', 'profile.name': 'sender',
'purchased.plan.customerId': 'customer-id', 'purchased.plan.customerId': 'customer-id',

View File

@@ -2,6 +2,8 @@ import { v4 as generateUUID } from 'uuid';
import { import {
generateUser, generateUser,
translate as t, translate as t,
createAndPopulateGroup,
generateChallenge,
} from '../../../../helpers/api-integration/v3'; } from '../../../../helpers/api-integration/v3';
describe('GET /tasks/:id', () => { describe('GET /tasks/:id', () => {
@@ -11,55 +13,158 @@ describe('GET /tasks/:id', () => {
user = await generateUser(); user = await generateUser();
}); });
context('task can be accessed', async () => { context('general', () => {
let task; context('task cannot be accessed', () => {
it('cannot get a non-existent task', async () => {
const dummyId = generateUUID();
beforeEach(async () => { await expect(user.get(`/tasks/${dummyId}`)).to.eventually.be.rejected.and.eql({
task = await user.post('/tasks/user', { code: 404,
text: 'test habit', error: 'NotFound',
type: 'habit', message: t('taskNotFound'),
alias: 'alias', });
}); });
}); });
it('gets specified task', async () => {
const getTask = await user.get(`/tasks/${task._id}`);
expect(getTask).to.eql(task);
});
it('can use alias to retrieve task', async () => {
const getTask = await user.get(`/tasks/${task.alias}`);
expect(getTask).to.eql(task);
});
// TODO after challenges are implemented
it('can get active challenge task that user does not own'); // Yes?
}); });
context('task cannot be accessed', () => { context('user', () => {
it('cannot get a non-existent task', async () => { context('task can be accessed', async () => {
const dummyId = generateUUID(); let task;
await expect(user.get(`/tasks/${dummyId}`)).to.eventually.be.rejected.and.eql({ beforeEach(async () => {
code: 404, task = await user.post('/tasks/user', {
error: 'NotFound', text: 'test habit',
message: t('taskNotFound'), type: 'habit',
alias: 'alias',
});
});
it('gets specified task', async () => {
const getTask = await user.get(`/tasks/${task._id}`);
expect(getTask).to.eql(task);
});
it('can use alias to retrieve task', async () => {
const getTask = await user.get(`/tasks/${task.alias}`);
expect(getTask).to.eql(task);
}); });
}); });
it('cannot get a task owned by someone else', async () => { context('task cannot be accessed', () => {
const anotherUser = await generateUser(); it('cannot get a task owned by someone else', async () => {
const task = await user.post('/tasks/user', { const anotherUser = await generateUser();
const task = await user.post('/tasks/user', {
text: 'test habit',
type: 'habit',
});
await expect(anotherUser.get(`/tasks/${task._id}`)).to.eventually.be.rejected.and.eql({
code: 404,
error: 'NotFound',
message: t('taskNotFound'),
});
});
});
});
context('challenge', () => {
let challenge; let task; let leader; let member;
before(async () => {
const populatedGroup = await createAndPopulateGroup({
members: 1,
});
const guild = populatedGroup.group;
leader = populatedGroup.groupLeader;
member = populatedGroup.members[0]; // eslint-disable-line prefer-destructuring
challenge = await generateChallenge(leader, guild);
await leader.post(`/challenges/${challenge._id}/join`);
task = await leader.post(`/tasks/challenge/${challenge._id}`, {
text: 'test habit', text: 'test habit',
type: 'habit', type: 'habit',
}); });
});
await expect(anotherUser.get(`/tasks/${task._id}`)).to.eventually.be.rejected.and.eql({ context('task can be accessed', async () => {
code: 404, it('can get challenge task if member of that challenge', async () => {
error: 'NotFound', await member.post(`/challenges/${challenge._id}/join`);
message: t('taskNotFound'),
const getTask = await member.get(`/tasks/${task._id}`);
expect(getTask).to.eql(task);
});
it('can get challenge task if leader of that challenge', async () => {
const getTask = await leader.get(`/tasks/${task._id}`);
expect(getTask).to.eql(task);
});
it('can get challenge task if admin', async () => {
const admin = await generateUser({
'contributor.admin': true,
});
const getTask = await admin.get(`/tasks/${task._id}`);
expect(getTask).to.eql(task);
});
});
context('task cannot be accessed', () => {
it('cannot get a task in a challenge i am not part of', async () => {
await expect(user.get(`/tasks/${task._id}`)).to.eventually.be.rejected.and.eql({
code: 404,
error: 'NotFound',
message: t('taskNotFound'),
});
});
});
});
context('group', () => {
let group; let task; let members; let leader;
before(async () => {
const groupData = await createAndPopulateGroup({
groupDetails: {
name: 'Test Guild',
type: 'guild',
},
members: 1,
});
group = groupData.group;
members = groupData.members;
leader = groupData.groupLeader;
task = await leader.post(`/tasks/group/${group._id}`, {
text: 'test habit',
type: 'habit',
});
});
context('task can be accessed', async () => {
it('can get group task if leader of that group', async () => {
const getTask = await leader.get(`/tasks/${task._id}`);
expect(getTask).to.eql(task);
});
it('can get group task if member of that group', async () => {
const getTask = await members[0].get(`/tasks/${task._id}`);
expect(getTask).to.eql(task);
});
});
context('task cannot be accessed', () => {
it('cannot get a task in a group i am not part of', async () => {
await expect(user.get(`/tasks/${task._id}`)).to.eventually.be.rejected.and.eql({
code: 404,
error: 'NotFound',
message: t('taskNotFound'),
});
}); });
}); });
}); });

View File

@@ -153,40 +153,12 @@ describe('GET /tasks/user', () => {
}); });
xit('returns dailies with isDue for the date specified and will add CDS offset if time is not supplied and assumes timezones', async () => { xit('returns dailies with isDue for the date specified and will add CDS offset if time is not supplied and assumes timezones', async () => {
const timezone = 420; const timezoneOffset = 420;
await user.update({ await user.update({
'preferences.dayStart': 0, 'preferences.dayStart': 0,
'preferences.timezoneOffset': timezone, 'preferences.timezoneOffset': timezoneOffset,
}); });
const startDate = moment().zone(timezone).subtract('4', 'days').startOf('day') const startDate = moment().utcOffset(-timezoneOffset).subtract('4', 'days').startOf('day')
.toISOString();
await user.post('/tasks/user', [
{
text: 'test daily',
type: 'daily',
startDate,
frequency: 'daily',
everyX: 2,
},
]);
const today = moment().format('YYYY-MM-DD');
const dailys = await user.get(`/tasks/user?type=dailys&dueDate=${today}`);
expect(dailys[0].isDue).to.be.true;
const yesterday = moment().subtract('1', 'days').format('YYYY-MM-DD');
const dailys2 = await user.get(`/tasks/user?type=dailys&dueDate=${yesterday}`);
expect(dailys2[0].isDue).to.be.false;
});
xit('returns dailies with isDue for the date specified and will add CDS offset if time is not supplied and assumes timezones', async () => {
const timezone = 240;
await user.update({
'preferences.dayStart': 0,
'preferences.timezoneOffset': timezone,
});
const startDate = moment().zone(timezone).subtract('4', 'days').startOf('day')
.toISOString(); .toISOString();
await user.post('/tasks/user', [ await user.post('/tasks/user', [
{ {
@@ -208,12 +180,39 @@ describe('GET /tasks/user', () => {
}); });
xit('returns dailies with isDue for the date specified and will add CDS offset if time is not supplied and assumes timezones', async () => { xit('returns dailies with isDue for the date specified and will add CDS offset if time is not supplied and assumes timezones', async () => {
const timezone = 540; const timezoneOffset = 240;
await user.update({ await user.update({
'preferences.dayStart': 0, 'preferences.dayStart': 0,
'preferences.timezoneOffset': timezone, 'preferences.timezoneOffset': timezoneOffset,
}); });
const startDate = moment().zone(timezone).subtract('4', 'days').startOf('day') const startDate = moment().utcOffset(-timezoneOffset).subtract('4', 'days').startOf('day')
.toISOString();
await user.post('/tasks/user', [
{
text: 'test daily',
type: 'daily',
startDate,
frequency: 'daily',
everyX: 2,
},
]);
const today = moment().format('YYYY-MM-DD');
const dailys = await user.get(`/tasks/user?type=dailys&dueDate=${today}`);
expect(dailys[0].isDue).to.be.true;
const yesterday = moment().subtract('1', 'days').format('YYYY-MM-DD');
const dailys2 = await user.get(`/tasks/user?type=dailys&dueDate=${yesterday}`);
expect(dailys2[0].isDue).to.be.false;
});
xit('returns dailies with isDue for the date specified and will add CDS offset if time is not supplied and assumes timezones', async () => {
const timezoneOffset = 540;
await user.update({
'preferences.dayStart': 0,
'preferences.timezoneOffset': timezoneOffset,
});
const startDate = moment().utcOffset(-timezoneOffset).subtract('4', 'days').startOf('day')
.toISOString(); .toISOString();
await user.post('/tasks/user', [ await user.post('/tasks/user', [
{ {

View File

@@ -1,4 +1,5 @@
import { v4 as generateUUID } from 'uuid'; import { v4 as generateUUID } from 'uuid';
import apiError from '../../../../../website/server/libs/apiError';
import { import {
generateUser, generateUser,
sleep, sleep,
@@ -44,7 +45,7 @@ describe('POST /tasks/:id/score/:direction', () => {
await expect(user.post(`/tasks/${generateUUID()}/score/tt`)).to.eventually.be.rejected.and.eql({ await expect(user.post(`/tasks/${generateUUID()}/score/tt`)).to.eventually.be.rejected.and.eql({
code: 400, code: 400,
error: 'BadRequest', error: 'BadRequest',
message: t('invalidReqParams'), message: apiError('directionUpDown'),
}); });
}); });
@@ -261,6 +262,7 @@ describe('POST /tasks/:id/score/:direction', () => {
const task = await user.get(`/tasks/${daily._id}`); const task = await user.get(`/tasks/${daily._id}`);
expect(task.completed).to.equal(true); expect(task.completed).to.equal(true);
expect(task.value).to.be.greaterThan(daily.value);
}); });
it('uncompletes daily when direction is down', async () => { it('uncompletes daily when direction is down', async () => {

View File

@@ -687,7 +687,6 @@ describe('POST /tasks/user', () => {
}); });
}); });
it('can create checklists', async () => { it('can create checklists', async () => {
const task = await user.post('/tasks/user', { const task = await user.post('/tasks/user', {
text: 'test daily', text: 'test daily',

View File

@@ -499,6 +499,45 @@ describe('PUT /tasks/:id', () => {
}); });
}); });
context('monthly dailys', () => {
let monthly;
beforeEach(async () => {
const date1 = moment.utc('2020-07-01').toDate();
monthly = await user.post('/tasks/user', {
text: 'test monthly',
type: 'daily',
frequency: 'monthly',
startDate: date1,
daysOfMonth: [date1.getDate()],
});
});
it('updates days of month when start date updated', async () => {
const date2 = moment.utc('2020-07-01').toDate();
const savedMonthly = await user.put(`/tasks/${monthly._id}`, {
startDate: date2,
});
expect(savedMonthly.daysOfMonth).to.deep.equal([moment(date2).date()]);
});
it('updates next due when start date updated', async () => {
const date2 = moment.utc('2022-07-01').toDate();
const savedMonthly = await user.put(`/tasks/${monthly._id}`, {
startDate: date2,
});
expect(savedMonthly.nextDue.length).to.eql(6);
expect(moment(savedMonthly.nextDue[0]).toDate()).to.eql(moment.utc('2022-08-01').toDate());
expect(moment(savedMonthly.nextDue[1]).toDate()).to.eql(moment.utc('2022-09-01').toDate());
expect(moment(savedMonthly.nextDue[2]).toDate()).to.eql(moment.utc('2022-10-01').toDate());
expect(moment(savedMonthly.nextDue[3]).toDate()).to.eql(moment.utc('2022-11-01').toDate());
expect(moment(savedMonthly.nextDue[4]).toDate()).to.eql(moment.utc('2022-12-01').toDate());
expect(moment(savedMonthly.nextDue[5]).toDate()).to.eql(moment.utc('2023-01-01').toDate());
});
});
context('rewards', () => { context('rewards', () => {
let reward; let reward;

View File

@@ -73,12 +73,7 @@ describe('Groups DELETE /tasks/:id', () => {
}); });
const memberTasks = await member.get('/tasks/user'); const memberTasks = await member.get('/tasks/user');
const syncedTask = find(memberTasks, findAssignedTask); const syncedTask = find(memberTasks, findAssignedTask);
await expect(member.post(`/tasks/${syncedTask._id}/score/up`)) await member.post(`/tasks/${syncedTask._id}/score/up`);
.to.eventually.be.rejected.and.to.eql({
code: 401,
error: 'NotAuthorized',
message: t('taskApprovalHasBeenRequested'),
});
await user.sync(); await user.sync();
await member2.sync(); await member2.sync();
@@ -96,16 +91,16 @@ describe('Groups DELETE /tasks/:id', () => {
expect(member2.notifications.length).to.equal(1); expect(member2.notifications.length).to.equal(1);
}); });
it('unlinks assigned user', async () => { it('deletes task from assigned user', async () => {
await user.del(`/tasks/${task._id}`); await user.del(`/tasks/${task._id}`);
const memberTasks = await member.get('/tasks/user'); const memberTasks = await member.get('/tasks/user');
const syncedTask = find(memberTasks, findAssignedTask); const syncedTask = find(memberTasks, findAssignedTask);
expect(syncedTask.group.broken).to.equal('TASK_DELETED'); expect(syncedTask).to.not.exist;
}); });
it('unlinks all assigned users', async () => { it('deletes task from all assigned users', async () => {
await user.del(`/tasks/${task._id}`); await user.del(`/tasks/${task._id}`);
const memberTasks = await member.get('/tasks/user'); const memberTasks = await member.get('/tasks/user');
@@ -114,8 +109,8 @@ describe('Groups DELETE /tasks/:id', () => {
const member2Tasks = await member2.get('/tasks/user'); const member2Tasks = await member2.get('/tasks/user');
const member2SyncedTask = find(member2Tasks, findAssignedTask); const member2SyncedTask = find(member2Tasks, findAssignedTask);
expect(syncedTask.group.broken).to.equal('TASK_DELETED'); expect(syncedTask).to.not.exist;
expect(member2SyncedTask.group.broken).to.equal('TASK_DELETED'); expect(member2SyncedTask).to.not.exist;
}); });
it('prevents a user from deleting a task they are assigned to', async () => { it('prevents a user from deleting a task they are assigned to', async () => {
@@ -130,22 +125,6 @@ describe('Groups DELETE /tasks/:id', () => {
}); });
}); });
it('allows a user to delete a broken task', async () => {
const memberTasks = await member.get('/tasks/user');
const 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 () => { it('allows a user to delete a task after leaving a group', async () => {
const memberTasks = await member.get('/tasks/user'); const memberTasks = await member.get('/tasks/user');
const syncedTask = find(memberTasks, findAssignedTask); const syncedTask = find(memberTasks, findAssignedTask);

View File

@@ -58,22 +58,14 @@ describe('POST /tasks/:id/approve/:userId', () => {
let memberTasks = await member.get('/tasks/user'); let memberTasks = await member.get('/tasks/user');
let syncedTask = find(memberTasks, findAssignedTask); let syncedTask = find(memberTasks, findAssignedTask);
await expect(member.post(`/tasks/${syncedTask._id}/score/up`)) await member.post(`/tasks/${syncedTask._id}/score/up`);
.to.eventually.be.rejected.and.to.eql({
code: 401,
error: 'NotAuthorized',
message: t('taskApprovalHasBeenRequested'),
});
await user.post(`/tasks/${task._id}/approve/${member._id}`); await user.post(`/tasks/${task._id}/approve/${member._id}`);
await member.sync(); await member.sync();
expect(member.notifications.length).to.equal(3); expect(member.notifications.length).to.equal(2);
expect(member.notifications[1].type).to.equal('GROUP_TASK_APPROVED'); expect(member.notifications[1].type).to.equal('GROUP_TASK_APPROVED');
expect(member.notifications[1].data.message).to.equal(t('yourTaskHasBeenApproved', { taskText: task.text })); expect(member.notifications[1].data.message).to.equal(t('yourTaskHasBeenApproved', { taskText: task.text }));
expect(member.notifications[2].type).to.equal('SCORED_TASK');
expect(member.notifications[2].data.message).to.equal(t('yourTaskHasBeenApproved', { taskText: task.text }));
memberTasks = await member.get('/tasks/user'); memberTasks = await member.get('/tasks/user');
syncedTask = find(memberTasks, findAssignedTask); syncedTask = find(memberTasks, findAssignedTask);
@@ -93,21 +85,13 @@ describe('POST /tasks/:id/approve/:userId', () => {
let memberTasks = await member.get('/tasks/user'); let memberTasks = await member.get('/tasks/user');
let syncedTask = find(memberTasks, findAssignedTask); let syncedTask = find(memberTasks, findAssignedTask);
await expect(member.post(`/tasks/${syncedTask._id}/score/up`)) await member.post(`/tasks/${syncedTask._id}/score/up`);
.to.eventually.be.rejected.and.to.eql({
code: 401,
error: 'NotAuthorized',
message: t('taskApprovalHasBeenRequested'),
});
await member2.post(`/tasks/${task._id}/approve/${member._id}`); await member2.post(`/tasks/${task._id}/approve/${member._id}`);
await member.sync(); await member.sync();
expect(member.notifications.length).to.equal(3); expect(member.notifications.length).to.equal(2);
expect(member.notifications[1].type).to.equal('GROUP_TASK_APPROVED'); expect(member.notifications[1].type).to.equal('GROUP_TASK_APPROVED');
expect(member.notifications[1].data.message).to.equal(t('yourTaskHasBeenApproved', { taskText: task.text })); expect(member.notifications[1].data.message).to.equal(t('yourTaskHasBeenApproved', { taskText: task.text }));
expect(member.notifications[2].type).to.equal('SCORED_TASK');
expect(member.notifications[2].data.message).to.equal(t('yourTaskHasBeenApproved', { taskText: task.text }));
memberTasks = await member.get('/tasks/user'); memberTasks = await member.get('/tasks/user');
syncedTask = find(memberTasks, findAssignedTask); syncedTask = find(memberTasks, findAssignedTask);
@@ -125,12 +109,7 @@ describe('POST /tasks/:id/approve/:userId', () => {
await member2.post(`/tasks/${task._id}/assign/${member._id}`); await member2.post(`/tasks/${task._id}/assign/${member._id}`);
const memberTasks = await member.get('/tasks/user'); const memberTasks = await member.get('/tasks/user');
const syncedTask = find(memberTasks, findAssignedTask); const syncedTask = find(memberTasks, findAssignedTask);
await expect(member.post(`/tasks/${syncedTask._id}/score/up`)) await member.post(`/tasks/${syncedTask._id}/score/up`);
.to.eventually.be.rejected.and.to.eql({
code: 401,
error: 'NotAuthorized',
message: t('taskApprovalHasBeenRequested'),
});
await user.sync(); await user.sync();
await member2.sync(); await member2.sync();
@@ -157,14 +136,9 @@ describe('POST /tasks/:id/approve/:userId', () => {
const memberTasks = await member.get('/tasks/user'); const memberTasks = await member.get('/tasks/user');
const syncedTask = find(memberTasks, findAssignedTask); const syncedTask = find(memberTasks, findAssignedTask);
await expect(member.post(`/tasks/${syncedTask._id}/score/up`)) await member.post(`/tasks/${syncedTask._id}/score/up`);
.to.eventually.be.rejected.and.to.eql({
code: 401,
error: 'NotAuthorized',
message: t('taskApprovalHasBeenRequested'),
});
await member2.post(`/tasks/${task._id}/approve/${member._id}`); await member2.post(`/tasks/${task._id}/approve/${member._id}`);
await expect(user.post(`/tasks/${task._id}/approve/${member._id}`)) await expect(user.post(`/tasks/${task._id}/approve/${member._id}`))
.to.eventually.be.rejected.and.to.eql({ .to.eventually.be.rejected.and.to.eql({
code: 401, code: 401,
@@ -197,13 +171,7 @@ describe('POST /tasks/:id/approve/:userId', () => {
const memberTasks = await member.get('/tasks/user'); const memberTasks = await member.get('/tasks/user');
const syncedTask = find(memberTasks, findAssignedTask); const syncedTask = find(memberTasks, findAssignedTask);
await expect(member.post(`/tasks/${syncedTask._id}/score/up`)) await member.post(`/tasks/${syncedTask._id}/score/up`);
.to.eventually.be.rejected.and.to.eql({
code: 401,
error: 'NotAuthorized',
message: t('taskApprovalHasBeenRequested'),
});
await user.post(`/tasks/${sharedCompletionTask._id}/approve/${member._id}`); await user.post(`/tasks/${sharedCompletionTask._id}/approve/${member._id}`);
const groupTasks = await user.get(`/tasks/group/${guild._id}?type=completedTodos`); const groupTasks = await user.get(`/tasks/group/${guild._id}?type=completedTodos`);
@@ -226,13 +194,7 @@ describe('POST /tasks/:id/approve/:userId', () => {
const memberTasks = await member.get('/tasks/user'); const memberTasks = await member.get('/tasks/user');
const syncedTask = find(memberTasks, findAssignedTask); const syncedTask = find(memberTasks, findAssignedTask);
await expect(member.post(`/tasks/${syncedTask._id}/score/up`)) await member.post(`/tasks/${syncedTask._id}/score/up`);
.to.eventually.be.rejected.and.to.eql({
code: 401,
error: 'NotAuthorized',
message: t('taskApprovalHasBeenRequested'),
});
await user.post(`/tasks/${sharedCompletionTask._id}/approve/${member._id}`); await user.post(`/tasks/${sharedCompletionTask._id}/approve/${member._id}`);
const member2Tasks = await member2.get('/tasks/user'); const member2Tasks = await member2.get('/tasks/user');
@@ -258,13 +220,7 @@ describe('POST /tasks/:id/approve/:userId', () => {
const memberTasks = await member.get('/tasks/user'); const memberTasks = await member.get('/tasks/user');
const syncedTask = find(memberTasks, findAssignedTask); const syncedTask = find(memberTasks, findAssignedTask);
await expect(member.post(`/tasks/${syncedTask._id}/score/up`)) await member.post(`/tasks/${syncedTask._id}/score/up`);
.to.eventually.be.rejected.and.to.eql({
code: 401,
error: 'NotAuthorized',
message: t('taskApprovalHasBeenRequested'),
});
await user.post(`/tasks/${sharedCompletionTask._id}/approve/${member._id}`); await user.post(`/tasks/${sharedCompletionTask._id}/approve/${member._id}`);
const groupTasks = await user.get(`/tasks/group/${guild._id}`); const groupTasks = await user.get(`/tasks/group/${guild._id}`);
@@ -287,21 +243,10 @@ describe('POST /tasks/:id/approve/:userId', () => {
const memberTasks = await member.get('/tasks/user'); const memberTasks = await member.get('/tasks/user');
const syncedTask = find(memberTasks, findAssignedTask); const syncedTask = find(memberTasks, findAssignedTask);
await expect(member.post(`/tasks/${syncedTask._id}/score/up`)) await member.post(`/tasks/${syncedTask._id}/score/up`);
.to.eventually.be.rejected.and.to.eql({
code: 401,
error: 'NotAuthorized',
message: t('taskApprovalHasBeenRequested'),
});
const member2Tasks = await member2.get('/tasks/user'); const member2Tasks = await member2.get('/tasks/user');
const member2SyncedTask = find(member2Tasks, findAssignedTask); const member2SyncedTask = find(member2Tasks, findAssignedTask);
await expect(member2.post(`/tasks/${member2SyncedTask._id}/score/up`)) await member2.post(`/tasks/${member2SyncedTask._id}/score/up`);
.to.eventually.be.rejected.and.to.eql({
code: 401,
error: 'NotAuthorized',
message: t('taskApprovalHasBeenRequested'),
});
await user.post(`/tasks/${sharedCompletionTask._id}/approve/${member._id}`); await user.post(`/tasks/${sharedCompletionTask._id}/approve/${member._id}`);
await user.post(`/tasks/${sharedCompletionTask._id}/approve/${member2._id}`); await user.post(`/tasks/${sharedCompletionTask._id}/approve/${member2._id}`);

View File

@@ -61,13 +61,7 @@ describe('POST /tasks/:id/needs-work/:userId', () => {
let syncedTask = find(memberTasks, findAssignedTask); let syncedTask = find(memberTasks, findAssignedTask);
// score task to require approval // score task to require approval
await expect(member.post(`/tasks/${syncedTask._id}/score/up`)) await member.post(`/tasks/${syncedTask._id}/score/up`);
.to.eventually.be.rejected.and.to.eql({
code: 401,
error: 'NotAuthorized',
message: t('taskApprovalHasBeenRequested'),
});
await user.post(`/tasks/${task._id}/needs-work/${member._id}`); await user.post(`/tasks/${task._id}/needs-work/${member._id}`);
[memberTasks] = await Promise.all([member.get('/tasks/user'), member.sync()]); [memberTasks] = await Promise.all([member.get('/tasks/user'), member.sync()]);
@@ -114,12 +108,7 @@ describe('POST /tasks/:id/needs-work/:userId', () => {
let syncedTask = find(memberTasks, findAssignedTask); let syncedTask = find(memberTasks, findAssignedTask);
// score task to require approval // score task to require approval
await expect(member.post(`/tasks/${syncedTask._id}/score/up`)) await member.post(`/tasks/${syncedTask._id}/score/up`);
.to.eventually.be.rejected.and.to.eql({
code: 401,
error: 'NotAuthorized',
message: t('taskApprovalHasBeenRequested'),
});
const initialNotifications = member.notifications.length; const initialNotifications = member.notifications.length;
@@ -172,13 +161,7 @@ describe('POST /tasks/:id/needs-work/:userId', () => {
const memberTasks = await member.get('/tasks/user'); const memberTasks = await member.get('/tasks/user');
const syncedTask = find(memberTasks, findAssignedTask); const syncedTask = find(memberTasks, findAssignedTask);
await expect(member.post(`/tasks/${syncedTask._id}/score/up`)) await member.post(`/tasks/${syncedTask._id}/score/up`);
.to.eventually.be.rejected.and.to.eql({
code: 401,
error: 'NotAuthorized',
message: t('taskApprovalHasBeenRequested'),
});
await member2.post(`/tasks/${task._id}/approve/${member._id}`); await member2.post(`/tasks/${task._id}/approve/${member._id}`);
await expect(user.post(`/tasks/${task._id}/needs-work/${member._id}`)) await expect(user.post(`/tasks/${task._id}/needs-work/${member._id}`))
.to.eventually.be.rejected.and.to.eql({ .to.eventually.be.rejected.and.to.eql({

View File

@@ -44,12 +44,11 @@ describe('POST /tasks/:id/score/:direction', () => {
const syncedTask = find(memberTasks, findAssignedTask); const syncedTask = find(memberTasks, findAssignedTask);
const direction = 'up'; const direction = 'up';
await expect(member.post(`/tasks/${syncedTask._id}/score/${direction}`)) const response = await member.post(`/tasks/${syncedTask._id}/score/${direction}`);
.to.eventually.be.rejected.and.to.eql({
code: 401, expect(response.data.requiresApproval).to.equal(true);
error: 'NotAuthorized', expect(response.message).to.equal(t('taskApprovalHasBeenRequested'));
message: t('taskApprovalHasBeenRequested'),
});
const updatedTask = await member.get(`/tasks/${syncedTask._id}`); const updatedTask = await member.get(`/tasks/${syncedTask._id}`);
await user.sync(); await user.sync();
@@ -76,12 +75,7 @@ describe('POST /tasks/:id/score/:direction', () => {
const syncedTask = find(memberTasks, findAssignedTask); const syncedTask = find(memberTasks, findAssignedTask);
const direction = 'up'; const direction = 'up';
await expect(member.post(`/tasks/${syncedTask._id}/score/${direction}`)) await member.post(`/tasks/${syncedTask._id}/score/${direction}`);
.to.eventually.be.rejected.and.to.eql({
code: 401,
error: 'NotAuthorized',
message: t('taskApprovalHasBeenRequested'),
});
const updatedTask = await member.get(`/tasks/${syncedTask._id}`); const updatedTask = await member.get(`/tasks/${syncedTask._id}`);
await user.sync(); await user.sync();
await member2.sync(); await member2.sync();
@@ -111,31 +105,18 @@ describe('POST /tasks/:id/score/:direction', () => {
const memberTasks = await member.get('/tasks/user'); const memberTasks = await member.get('/tasks/user');
const syncedTask = find(memberTasks, findAssignedTask); const syncedTask = find(memberTasks, findAssignedTask);
await expect(member.post(`/tasks/${syncedTask._id}/score/up`)) await 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`)) const response = await member.post(`/tasks/${syncedTask._id}/score/up`);
.to.eventually.be.rejected.and.eql({ expect(response.data.requiresApproval).to.equal(true);
code: 401, expect(response.message).to.equal(t('taskRequiresApproval'));
error: 'NotAuthorized',
message: t('taskRequiresApproval'),
});
}); });
it('allows a user to score an approved task', async () => { it('allows a user to score an approved task', async () => {
const memberTasks = await member.get('/tasks/user'); const memberTasks = await member.get('/tasks/user');
const syncedTask = find(memberTasks, findAssignedTask); const syncedTask = find(memberTasks, findAssignedTask);
await expect(member.post(`/tasks/${syncedTask._id}/score/up`)) await member.post(`/tasks/${syncedTask._id}/score/up`);
.to.eventually.be.rejected.and.to.eql({
code: 401,
error: 'NotAuthorized',
message: t('taskApprovalHasBeenRequested'),
});
await user.post(`/tasks/${task._id}/approve/${member._id}`); await user.post(`/tasks/${task._id}/approve/${member._id}`);

View File

@@ -4,8 +4,12 @@ import {
} from '../../../../../helpers/api-integration/v3'; } from '../../../../../helpers/api-integration/v3';
describe('PUT /tasks/:id', () => { describe('PUT /tasks/:id', () => {
let user; let guild; let member; let member2; let let user;
task; let guild;
let member;
let member2;
let habit;
let todo;
function findAssignedTask (memberTask) { function findAssignedTask (memberTask) {
return memberTask.group.id === guild._id; return memberTask.group.id === guild._id;
@@ -25,7 +29,7 @@ describe('PUT /tasks/:id', () => {
member = members[0]; // eslint-disable-line prefer-destructuring member = members[0]; // eslint-disable-line prefer-destructuring
member2 = members[1]; // eslint-disable-line prefer-destructuring member2 = members[1]; // eslint-disable-line prefer-destructuring
task = await user.post(`/tasks/group/${guild._id}`, { habit = await user.post(`/tasks/group/${guild._id}`, {
text: 'test habit', text: 'test habit',
type: 'habit', type: 'habit',
up: false, up: false,
@@ -33,12 +37,18 @@ describe('PUT /tasks/:id', () => {
notes: 1976, notes: 1976,
}); });
await user.post(`/tasks/${task._id}/assign/${member._id}`); todo = await user.post(`/tasks/group/${guild._id}`, {
await user.post(`/tasks/${task._id}/assign/${member2._id}`); text: 'test todo',
type: 'todo',
notes: 1976,
});
await user.post(`/tasks/${habit._id}/assign/${member._id}`);
await user.post(`/tasks/${habit._id}/assign/${member2._id}`);
}); });
it('updates a group task', async () => { it('updates a group task', async () => {
const savedHabit = await user.put(`/tasks/${task._id}`, { const savedHabit = await user.put(`/tasks/${habit._id}`, {
notes: 'some new notes', notes: 'some new notes',
}); });
@@ -51,27 +61,55 @@ describe('PUT /tasks/:id', () => {
managerId: member._id, managerId: member._id,
}); });
// change the todo // change the habit
task = await member.put(`/tasks/${task._id}`, { habit = await member.put(`/tasks/${habit._id}`, {
text: 'new text!', text: 'new text!',
requiresApproval: true, requiresApproval: true,
}); });
const memberTasks = await member2.get('/tasks/user'); const memberTasks = await member2.get('/tasks/user');
const syncedTask = find(memberTasks, memberTask => memberTask.group.taskId === task._id); const syncedTask = find(memberTasks, memberTask => memberTask.group.taskId === habit._id);
// score up to trigger approval // score up to trigger approval
await expect(member2.post(`/tasks/${syncedTask._id}/score/up`)) const response = await member2.post(`/tasks/${syncedTask._id}/score/up`);
.to.eventually.be.rejected.and.to.eql({
code: 401, expect(response.data.requiresApproval).to.equal(true);
error: 'NotAuthorized', expect(response.message).to.equal(t('taskApprovalHasBeenRequested'));
message: t('taskApprovalHasBeenRequested'), });
});
it('member updates a group task value - not allowed', async () => {
// change the todo
await expect(member.put(`/tasks/${habit._id}`, {
text: 'new text!',
})).to.eventually.be.rejected.and.to.eql({
code: 401,
error: 'NotAuthorized',
message: t('onlyGroupLeaderCanEditTasks'),
});
});
it('member updates the collapseChecklist property - change is allowed', async () => {
// change the todo
await member.put(`/tasks/${todo._id}`, {
collapseChecklist: true,
});
});
it('member updates the collapseChecklist and another property - change not allowed', async () => {
// change the todo
await expect(member.put(`/tasks/${todo._id}`, {
collapseChecklist: true,
title: 'test',
})).to.eventually.be.rejected.and.to.eql({
code: 401,
error: 'NotAuthorized',
message: t('onlyGroupLeaderCanEditTasks'),
});
}); });
it('updates a group task with checklist', async () => { it('updates a group task with checklist', async () => {
// add a new todo // add a new todo
task = await user.post(`/tasks/group/${guild._id}`, { habit = await user.post(`/tasks/group/${guild._id}`, {
text: 'todo', text: 'todo',
type: 'todo', type: 'todo',
checklist: [ checklist: [
@@ -81,13 +119,13 @@ describe('PUT /tasks/:id', () => {
], ],
}); });
await user.post(`/tasks/${task._id}/assign/${member._id}`); await user.post(`/tasks/${habit._id}/assign/${member._id}`);
// change the checklist text // change the checklist text
task = await user.put(`/tasks/${task._id}`, { habit = await user.put(`/tasks/${habit._id}`, {
checklist: [ checklist: [
{ {
id: task.checklist[0].id, id: habit.checklist[0].id,
text: 'checklist 1 - edit', text: 'checklist 1 - edit',
}, },
{ {
@@ -96,18 +134,17 @@ describe('PUT /tasks/:id', () => {
], ],
}); });
expect(task.checklist.length).to.eql(2); expect(habit.checklist.length).to.eql(2);
}); });
it('updates the linked tasks', async () => { it('updates the linked tasks', async () => {
await user.put(`/tasks/${task._id}`, { await user.put(`/tasks/${habit._id}`, {
text: 'some new text', text: 'some new text',
up: false, up: false,
down: false, down: false,
notes: 'some new notes', notes: 'some new notes',
}); });
const memberTasks = await member.get('/tasks/user'); const memberTasks = await member.get('/tasks/user');
const syncedTask = find(memberTasks, findAssignedTask); const syncedTask = find(memberTasks, findAssignedTask);
@@ -117,7 +154,7 @@ describe('PUT /tasks/:id', () => {
}); });
it('updates the linked tasks for all assigned users', async () => { it('updates the linked tasks for all assigned users', async () => {
await user.put(`/tasks/${task._id}`, { await user.put(`/tasks/${habit._id}`, {
text: 'some new text', text: 'some new text',
up: false, up: false,
down: false, down: false,
@@ -144,14 +181,13 @@ describe('PUT /tasks/:id', () => {
managerId: member2._id, managerId: member2._id,
}); });
await member2.put(`/tasks/${task._id}`, { await member2.put(`/tasks/${habit._id}`, {
text: 'some new text', text: 'some new text',
up: false, up: false,
down: false, down: false,
notes: 'some new notes', notes: 'some new notes',
}); });
const memberTasks = await member.get('/tasks/user'); const memberTasks = await member.get('/tasks/user');
const syncedTask = find(memberTasks, findAssignedTask); const syncedTask = find(memberTasks, findAssignedTask);

View File

@@ -1,4 +1,3 @@
import { v4 as generateUUID } from 'uuid'; import { v4 as generateUUID } from 'uuid';
import { find } from 'lodash'; import { find } from 'lodash';
import { import {
@@ -162,6 +161,23 @@ describe('POST /user/class/cast/:spellId', () => {
}); });
}); });
it('Issue #12361: returns an error if stealth has already been cast', async () => {
await user.update({
'stats.class': 'rogue',
'stats.lvl': 15,
'stats.mp': 400,
'stats.buffs.stealth': 1,
});
await user.sync();
await expect(user.post('/user/class/cast/stealth'))
.to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('spellAlreadyCast'),
});
expect(user.stats.mp).to.equal(400);
});
it('returns an error if targeted party member doesn\'t exist', async () => { it('returns an error if targeted party member doesn\'t exist', async () => {
const { groupLeader } = await createAndPopulateGroup({ const { groupLeader } = await createAndPopulateGroup({
groupDetails: { type: 'party', privacy: 'private' }, groupDetails: { type: 'party', privacy: 'private' },
@@ -325,7 +341,6 @@ describe('POST /user/class/cast/:spellId', () => {
expect(result.user.stats.mp).to.equal(10); expect(result.user.stats.mp).to.equal(10);
}); });
// TODO find a way to have sinon working in integration tests // TODO find a way to have sinon working in integration tests
// it doesn't work when tests are running separately from server // it doesn't work when tests are running separately from server
it('passes correct target to spell when targetType === \'tasks\''); it('passes correct target to spell when targetType === \'tasks\'');

View File

@@ -41,6 +41,29 @@ describe('POST /user/feed/:pet/:food', () => {
expect(user.items.pets['Wolf-Base']).to.equal(7); expect(user.items.pets['Wolf-Base']).to.equal(7);
}); });
it('bulk feeding pet with non-preferred food', async () => {
await user.update({
'items.pets.Wolf-Base': 5,
'items.food.Milk': 3,
});
const food = content.food.Milk;
const pet = content.petInfo['Wolf-Base'];
const res = await user.post('/user/feed/Wolf-Base/Milk?amount=2');
await user.sync();
expect(res).to.eql({
data: user.items.pets['Wolf-Base'],
message: t('messageDontEnjoyFood', {
egg: pet.text(),
foodText: food.textThe(),
}),
});
expect(user.items.food.Milk).to.eql(1);
expect(user.items.pets['Wolf-Base']).to.equal(9);
});
context('sending user activity webhooks', () => { context('sending user activity webhooks', () => {
before(async () => { before(async () => {
await server.start(); await server.start();
@@ -77,5 +100,33 @@ describe('POST /user/feed/:pet/:food', () => {
expect(body.pet).to.eql('Wolf-Base'); expect(body.pet).to.eql('Wolf-Base');
expect(body.message).to.eql(res.message); expect(body.message).to.eql(res.message);
}); });
it('sends user activity webhook (mount raised after full bulk feeding)', async () => {
const uuid = generateUUID();
await user.post('/user/webhook', {
url: `http://localhost:${server.port}/webhooks/${uuid}`,
type: 'userActivity',
enabled: true,
options: {
mountRaised: true,
},
});
await user.update({
'items.pets.Wolf-Base': 47,
'items.food.Milk': 3,
});
const res = await user.post('/user/feed/Wolf-Base/Milk?amount=2');
await sleep();
const body = server.getWebhookData(uuid);
expect(user.achievements.allYourBase).to.not.equal(true);
expect(body.type).to.eql('mountRaised');
expect(body.pet).to.eql('Wolf-Base');
expect(body.message).to.eql(res.message);
});
}); });
}); });

View File

@@ -4,7 +4,6 @@ import {
translate as t, translate as t,
} from '../../../../helpers/api-integration/v3'; } from '../../../../helpers/api-integration/v3';
describe('PUT /user', () => { describe('PUT /user', () => {
let user; let user;
@@ -53,7 +52,6 @@ describe('PUT /user', () => {
expect(user.tags.length).to.be.eql(userTags.length + 1); expect(user.tags.length).to.be.eql(userTags.length + 1);
}); });
it('validates profile.name', async () => { it('validates profile.name', async () => {
await expect(user.put('/user', { await expect(user.put('/user', {
'profile.name': ' ', // string should be trimmed 'profile.name': ' ', // string should be trimmed
@@ -94,6 +92,14 @@ describe('PUT /user', () => {
error: 'BadRequest', error: 'BadRequest',
message: t('displaynameIssueSlur'), message: t('displaynameIssueSlur'),
}); });
await expect(user.put('/user', {
'profile.name': 'namecontainsnewline\n',
})).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('displaynameIssueNewline'),
});
}); });
}); });

View File

@@ -10,7 +10,6 @@ import {
sha1Encrypt as sha1EncryptPassword, sha1Encrypt as sha1EncryptPassword,
} from '../../../../../../website/server/libs/password'; } from '../../../../../../website/server/libs/password';
describe('POST /user/auth/local/login', () => { describe('POST /user/auth/local/login', () => {
let api; let api;
let user; let user;

View File

@@ -9,7 +9,6 @@ import {
sha1Encrypt as sha1EncryptPassword, sha1Encrypt as sha1EncryptPassword,
} from '../../../../../../website/server/libs/password'; } from '../../../../../../website/server/libs/password';
const ENDPOINT = '/user/auth/update-email'; const ENDPOINT = '/user/auth/update-email';
describe('PUT /user/auth/update-email', () => { describe('PUT /user/auth/update-email', () => {

View File

@@ -127,19 +127,26 @@ describe('PUT /user/webhook/:id', () => {
it('can update taskActivity options', async () => { it('can update taskActivity options', async () => {
const type = 'taskActivity'; const type = 'taskActivity';
const options = { const options = {
checklistScored: true,
updated: false, updated: false,
deleted: true, scored: false,
}; };
const expected = {
const webhook = await user.put(`/user/webhook/${webhookToUpdate.id}`, { type, options }); checklistScored: true,
expect(webhook.options).to.eql({
checklistScored: false, // starting value
created: true, // starting value created: true, // starting value
updated: false, updated: false,
deleted: true, deleted: false, // starting value
scored: true, // default value scored: false,
}); };
const returnedWebhook = await user.put(`/user/webhook/${webhookToUpdate.id}`, { type, options });
await user.sync();
const savedWebhook = user.webhooks.find(hook => webhookToUpdate.id === hook.id);
expect(returnedWebhook.options).to.eql(expected);
expect(savedWebhook.options).to.eql(expected);
}); });
it('errors if taskActivity option is not a boolean', async () => { it('errors if taskActivity option is not a boolean', async () => {

View File

@@ -0,0 +1,583 @@
import { v4 as generateUUID } from 'uuid';
import {
generateUser,
sleep,
translate as t,
server,
} from '../../../helpers/api-integration/v4';
describe('POST /tasks/bulk-score', () => {
let user;
beforeEach(async () => {
user = await generateUser({
'stats.gp': 100,
});
});
context('all', () => {
it('can use id to identify the task', async () => {
const todo = await user.post('/tasks/user', {
text: 'test todo',
type: 'todo',
alias: 'alias',
});
const res = await user.post('/tasks/bulk-score', [{ id: todo.id, direction: 'up' }]);
expect(res).to.be.ok;
expect(res.tasks.length).to.equal(1);
expect(res.tasks[0].id).to.equal(todo._id);
expect(res.tasks[0].delta).to.be.greaterThan(0);
});
it('can use a alias in place of the id', async () => {
const todo = await user.post('/tasks/user', {
text: 'test todo',
type: 'todo',
alias: 'alias',
});
const res = await user.post('/tasks/bulk-score', [{ id: todo.alias, direction: 'up' }]);
expect(res).to.be.ok;
expect(res.tasks.length).to.equal(1);
expect(res.tasks[0].id).to.equal(todo._id);
expect(res.tasks[0].delta).to.be.greaterThan(0);
});
it('sends task scored webhooks', async () => {
const uuid = generateUUID();
await server.start();
await user.post('/user/webhook', {
url: `http://localhost:${server.port}/webhooks/${uuid}`,
type: 'taskActivity',
enabled: true,
options: {
created: false,
scored: true,
},
});
const task = await user.post('/tasks/user', {
text: 'test habit',
type: 'habit',
});
await user.post('/tasks/bulk-score', [{ id: task.id, direction: 'up' }]);
await sleep();
await server.close();
const body = server.getWebhookData(uuid);
expect(body.user).to.have.all.keys('_id', '_tmp', 'stats');
expect(body.user.stats).to.have.all.keys('hp', 'mp', 'exp', 'gp', 'lvl', 'class', 'points', 'str', 'con', 'int', 'per', 'buffs', 'training', 'maxHealth', 'maxMP', 'toNextLevel');
expect(body.task.id).to.eql(task.id);
expect(body.direction).to.eql('up');
expect(body.delta).to.be.greaterThan(0);
});
context('sending user activity webhooks', () => {
before(async () => {
await server.start();
});
after(async () => {
await server.close();
});
it('sends user activity webhook when the user levels up', async () => {
const uuid = generateUUID();
await user.post('/user/webhook', {
url: `http://localhost:${server.port}/webhooks/${uuid}`,
type: 'userActivity',
enabled: true,
options: {
leveledUp: true,
},
});
const initialLvl = user.stats.lvl;
await user.update({
'stats.exp': 3000,
});
const task = await user.post('/tasks/user', {
text: 'test habit',
type: 'habit',
});
await user.post('/tasks/bulk-score', [{ id: task.id, direction: 'up' }]);
await user.sync();
await sleep();
const body = server.getWebhookData(uuid);
expect(body.type).to.eql('leveledUp');
expect(body.initialLvl).to.eql(initialLvl);
expect(body.finalLvl).to.eql(user.stats.lvl);
});
});
it('fails the entire op if one task scoring fails', async () => {
const todo = await user.post('/tasks/user', {
text: 'test todo',
type: 'todo',
});
const habit = await user.post('/tasks/user', {
text: 'test habit',
type: 'habit',
});
await expect(user.post('/tasks/bulk-score', [
{ id: todo.id, direction: 'down' },
{ id: habit.id, direction: 'down' },
])).to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: t('sessionOutdated'),
});
const updatedHabit = await user.get(`/tasks/${habit._id}`);
expect(updatedHabit.history.length).to.equal(0);
expect(updatedHabit.value).to.equal(0);
const updatedTodo = await user.get(`/tasks/${todo._id}`);
expect(updatedTodo.value).to.equal(0);
});
it('sends _tmp for each task', async () => {
const habit1 = await user.post('/tasks/user', {
text: 'test habit 1',
type: 'habit',
});
const habit2 = await user.post('/tasks/user', {
text: 'test habit 2',
type: 'habit',
});
await user.update({
'party.quest.key': 'gryphon',
});
const res = await user.post('/tasks/bulk-score', [
{ id: habit1._id, direction: 'up' },
{ id: habit2._id, direction: 'up' },
]);
await user.sync();
expect(res.tasks[0]._tmp.quest.progressDelta).to.be.greaterThan(0);
expect(res.tasks[1]._tmp.quest.progressDelta).to.be.greaterThan(0);
expect(user.party.quest.progress.up).to
.eql(res.tasks[0]._tmp.quest.progressDelta + res.tasks[1]._tmp.quest.progressDelta);
});
});
context('todos', () => {
let todo;
beforeEach(async () => {
todo = await user.post('/tasks/user', {
text: 'test todo',
type: 'todo',
});
});
it('completes todo when direction is up', async () => {
await user.post('/tasks/bulk-score', [{ id: todo.id, direction: 'up' }]);
const task = await user.get(`/tasks/${todo._id}`);
expect(task.completed).to.equal(true);
expect(task.dateCompleted).to.be.a('string'); // date gets converted to a string as json doesn't have a Date type
});
it('moves completed todos out of user.tasksOrder.todos', async () => {
const getUser = await user.get('/user');
expect(getUser.tasksOrder.todos.indexOf(todo._id)).to.not.equal(-1);
await user.post('/tasks/bulk-score', [{ id: todo.id, direction: 'up' }]);
const updatedTask = await user.get(`/tasks/${todo._id}`);
expect(updatedTask.completed).to.equal(true);
const updatedUser = await user.get('/user');
expect(updatedUser.tasksOrder.todos.indexOf(todo._id)).to.equal(-1);
});
it('moves un-completed todos back into user.tasksOrder.todos', async () => {
const getUser = await user.get('/user');
expect(getUser.tasksOrder.todos.indexOf(todo._id)).to.not.equal(-1);
await user.post('/tasks/bulk-score', [{ id: todo.id, direction: 'up' }]);
await user.post('/tasks/bulk-score', [{ id: todo.id, direction: 'down' }]);
const updatedTask = await user.get(`/tasks/${todo._id}`);
expect(updatedTask.completed).to.equal(false);
const updatedUser = await user.get('/user');
const l = updatedUser.tasksOrder.todos.length;
expect(updatedUser.tasksOrder.todos.indexOf(todo._id)).not.to.equal(-1);
// Check that it was pushed at the bottom
expect(updatedUser.tasksOrder.todos.indexOf(todo._id)).to.equal(l - 1);
});
it('uncompletes todo when direction is down', async () => {
await user.post('/tasks/bulk-score', [{ id: todo.id, direction: 'up' }, { id: todo.id, direction: 'down' }]);
const updatedTask = await user.get(`/tasks/${todo._id}`);
expect(updatedTask.completed).to.equal(false);
expect(updatedTask.dateCompleted).to.be.a('undefined');
});
it('doesn\'t let a todo be uncompleted twice', async () => {
await expect(user.post('/tasks/bulk-score', [{ id: todo.id, direction: 'down' }])).to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: t('sessionOutdated'),
});
});
context('user stats when direction is up', () => {
let updatedUser; let res;
beforeEach(async () => {
res = await user.post('/tasks/bulk-score', [{ id: todo.id, direction: 'up' }]);
updatedUser = await user.get('/user');
});
it('increases user\'s mp', () => {
expect(updatedUser.stats.mp).to.be.greaterThan(user.stats.mp);
expect(res.mp).to.equal(updatedUser.stats.mp);
});
it('increases user\'s exp', () => {
expect(updatedUser.stats.exp).to.be.greaterThan(user.stats.exp);
expect(res.exp).to.equal(updatedUser.stats.exp);
});
it('increases user\'s gold', () => {
expect(updatedUser.stats.gp).to.be.greaterThan(user.stats.gp);
expect(res.gp).to.equal(updatedUser.stats.gp);
});
});
context('user stats when direction is down', () => {
let updatedUser; let initialUser; let res;
beforeEach(async () => {
await user.post('/tasks/bulk-score', [{ id: todo.id, direction: 'up' }]);
initialUser = await user.get('/user');
res = await user.post('/tasks/bulk-score', [{ id: todo.id, direction: 'down' }]);
updatedUser = await user.get('/user');
});
it('decreases user\'s mp', () => {
expect(updatedUser.stats.mp).to.be.lessThan(initialUser.stats.mp);
});
it('decreases user\'s exp', () => {
expect(updatedUser.stats.exp).to.be.lessThan(initialUser.stats.exp);
expect(res.exp).to.equal(updatedUser.stats.exp);
});
it('decreases user\'s gold', () => {
expect(updatedUser.stats.gp).to.be.lessThan(initialUser.stats.gp);
expect(res.gp).to.equal(updatedUser.stats.gp);
});
});
});
context('dailys', () => {
let daily;
beforeEach(async () => {
daily = await user.post('/tasks/user', {
text: 'test daily',
type: 'daily',
});
});
it('completes daily when direction is up', async () => {
await user.post('/tasks/bulk-score', [{ id: daily.id, direction: 'up' }]);
const task = await user.get(`/tasks/${daily._id}`);
expect(task.completed).to.equal(true);
});
it('uncompletes daily when direction is down', async () => {
await user.post('/tasks/bulk-score', [{ id: daily.id, direction: 'up' }, { id: daily.id, direction: 'down' }]);
const task = await user.get(`/tasks/${daily._id}`);
expect(task.completed).to.equal(false);
});
it('computes isDue', async () => {
await user.post('/tasks/bulk-score', [{ id: daily.id, direction: 'up' }]);
const task = await user.get(`/tasks/${daily._id}`);
expect(task.isDue).to.equal(true);
});
it('computes nextDue', async () => {
await user.post('/tasks/bulk-score', [{ id: daily.id, direction: 'up' }]);
const task = await user.get(`/tasks/${daily._id}`);
expect(task.nextDue.length).to.eql(6);
});
context('user stats when direction is up', () => {
let updatedUser; let res;
beforeEach(async () => {
res = await user.post('/tasks/bulk-score', [{ id: daily.id, direction: 'up' }]);
updatedUser = await user.get('/user');
});
it('increases user\'s mp', () => {
expect(updatedUser.stats.mp).to.be.greaterThan(user.stats.mp);
expect(res.mp).to.equal(updatedUser.stats.mp);
});
it('increases user\'s exp', () => {
expect(updatedUser.stats.exp).to.be.greaterThan(user.stats.exp);
expect(res.exp).to.equal(updatedUser.stats.exp);
});
it('increases user\'s gold', () => {
expect(updatedUser.stats.gp).to.be.greaterThan(user.stats.gp);
expect(res.gp).to.equal(updatedUser.stats.gp);
});
});
context('user stats when direction is down', () => {
let updatedUser; let initialUser; let res;
beforeEach(async () => {
await user.post('/tasks/bulk-score', [{ id: daily.id, direction: 'up' }]);
initialUser = await user.get('/user');
res = await user.post('/tasks/bulk-score', [{ id: daily.id, direction: 'down' }]);
updatedUser = await user.get('/user');
});
it('decreases user\'s mp', () => {
expect(updatedUser.stats.mp).to.be.lessThan(initialUser.stats.mp);
expect(res.mp).to.equal(updatedUser.stats.mp);
});
it('decreases user\'s exp', () => {
expect(updatedUser.stats.exp).to.be.lessThan(initialUser.stats.exp);
expect(res.exp).to.equal(updatedUser.stats.exp);
});
it('decreases user\'s gold', () => {
expect(updatedUser.stats.gp).to.be.lessThan(initialUser.stats.gp);
expect(res.gp).to.equal(updatedUser.stats.gp);
});
});
});
context('habits', () => {
let habit; let minusHabit; let plusHabit; let
neitherHabit; // eslint-disable-line no-unused-vars
beforeEach(async () => {
habit = await user.post('/tasks/user', {
text: 'test habit',
type: 'habit',
});
minusHabit = await user.post('/tasks/user', {
text: 'test min habit',
type: 'habit',
up: false,
});
plusHabit = await user.post('/tasks/user', {
text: 'test plus habit',
type: 'habit',
down: false,
});
neitherHabit = await user.post('/tasks/user', {
text: 'test neither habit',
type: 'habit',
up: false,
down: false,
});
});
it('increases user\'s mp when direction is up', async () => {
const res = await user.post('/tasks/bulk-score', [{ id: habit.id, direction: 'up' }, {
id: plusHabit.id,
direction: 'up',
}]);
const updatedUser = await user.get('/user');
expect(updatedUser.stats.mp).to.be.greaterThan(user.stats.mp);
expect(res.mp).to.equal(updatedUser.stats.mp);
});
it('decreases user\'s mp when direction is down', async () => {
const res = await user.post('/tasks/bulk-score', [{
id: habit.id,
direction: 'down',
}, {
id: minusHabit.id,
direction: 'down',
}]);
const updatedUser = await user.get('/user');
expect(updatedUser.stats.mp).to.be.lessThan(user.stats.mp);
expect(res.mp).to.equal(updatedUser.stats.mp);
});
it('increases user\'s exp when direction is up', async () => {
const res = await user.post('/tasks/bulk-score', [{
id: habit.id,
direction: 'up',
}, {
id: plusHabit.id,
direction: 'up',
}]);
const updatedUser = await user.get('/user');
expect(updatedUser.stats.exp).to.be.greaterThan(user.stats.exp);
expect(res.exp).to.equal(updatedUser.stats.exp);
});
it('increases user\'s gold when direction is up', async () => {
const res = await user.post('/tasks/bulk-score', [{
id: habit.id,
direction: 'up',
}, {
id: plusHabit.id,
direction: 'up',
}]);
const updatedUser = await user.get('/user');
expect(updatedUser.stats.gp).to.be.greaterThan(user.stats.gp);
expect(res.gp).to.equal(updatedUser.stats.gp);
});
it('records only one history entry per day', async () => {
const initialHistoryLength = habit.history.length;
await user.post('/tasks/bulk-score', [{
id: habit.id,
direction: 'up',
}, {
id: habit.id,
direction: 'up',
}, {
id: habit.id,
direction: 'down',
}, {
id: habit.id,
direction: 'up',
}]);
const updatedTask = await user.get(`/tasks/${habit._id}`);
expect(updatedTask.history.length).to.eql(initialHistoryLength + 1);
const lastHistoryEntry = updatedTask.history[updatedTask.history.length - 1];
expect(lastHistoryEntry.scoredUp).to.equal(3);
expect(lastHistoryEntry.scoredDown).to.equal(1);
});
});
context('mixed', () => {
let habit; let daily; let todo;
beforeEach(async () => {
habit = await user.post('/tasks/user', {
text: 'test habit',
type: 'habit',
});
daily = await user.post('/tasks/user', {
text: 'test habit',
type: 'habit',
});
todo = await user.post('/tasks/user', {
text: 'test habit',
type: 'habit',
});
});
it('scores habits, dailies, todos', async () => {
const res = await user.post('/tasks/bulk-score', [
{ id: habit.id, direction: 'down' },
{ id: daily.id, direction: 'up' },
{ id: todo.id, direction: 'up' },
]);
expect(res.tasks[0].id).to.eql(habit.id);
expect(res.tasks[0].delta).to.be.below(0);
expect(res.tasks[0]._tmp).to.exist;
expect(res.tasks[1].id).to.eql(daily.id);
expect(res.tasks[1].delta).to.be.greaterThan(0);
expect(res.tasks[1]._tmp).to.exist;
expect(res.tasks[2].id).to.eql(todo.id);
expect(res.tasks[2].delta).to.be.greaterThan(0);
expect(res.tasks[2]._tmp).to.exist;
const updatedHabit = await user.get(`/tasks/${habit._id}`);
const updatedDaily = await user.get(`/tasks/${daily._id}`);
const updatedTodo = await user.get(`/tasks/${todo._id}`);
expect(habit.value).to.be.greaterThan(updatedHabit.value);
expect(updatedHabit.counterDown).to.equal(1);
expect(updatedDaily.value).to.be.greaterThan(daily.value);
expect(updatedTodo.value).to.be.greaterThan(todo.value);
});
});
context('reward', () => {
it('correctly handles rewards', async () => {
const reward = await user.post('/tasks/user', {
text: 'test reward',
type: 'reward',
value: 5,
});
const res = await user.post('/tasks/bulk-score', [{ id: reward.id, direction: 'up' }]);
const updatedUser = await user.get('/user');
// purchases reward
expect(user.stats.gp).to.equal(updatedUser.stats.gp + 5);
expect(res.gp).to.equal(updatedUser.stats.gp);
// does not change user\'s mp
expect(user.stats.mp).to.equal(updatedUser.stats.mp);
expect(res.mp).to.equal(updatedUser.stats.mp);
// does not change user\'s exp
expect(user.stats.exp).to.equal(updatedUser.stats.exp);
expect(res.exp).to.equal(updatedUser.stats.exp);
});
it('fails if the user does not have enough gold', async () => {
const reward = await user.post('/tasks/user', {
text: 'test reward',
type: 'reward',
value: 500,
});
await expect(user.post('/tasks/bulk-score', [{ id: reward.id, direction: 'up' }])).to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: t('messageNotEnoughGold'),
});
const updatedUser = await user.get('/user');
// does not purchase reward
expect(user.stats.gp).to.equal(updatedUser.stats.gp);
});
});
});

View File

@@ -1,4 +1,3 @@
import { v4 as generateUUID } from 'uuid'; import { v4 as generateUUID } from 'uuid';
import { find } from 'lodash'; import { find } from 'lodash';
import { import {
@@ -310,7 +309,6 @@ describe('POST /user/class/cast/:spellId', () => {
expect(result.user.stats.mp).to.equal(10); expect(result.user.stats.mp).to.equal(10);
}); });
// TODO find a way to have sinon working in integration tests // TODO find a way to have sinon working in integration tests
// it doesn't work when tests are running separately from server // it doesn't work when tests are running separately from server
it('passes correct target to spell when targetType === \'tasks\''); it('passes correct target to spell when targetType === \'tasks\'');

View File

@@ -130,7 +130,6 @@ describe('POST /user/reset', () => {
}, },
}); });
await hero.post('/user/reset'); await hero.post('/user/reset');
const heroRes = await admin.get(`/hall/heroes/${hero.auth.local.username}`); const heroRes = await admin.get(`/hall/heroes/${hero.auth.local.username}`);

View File

@@ -4,7 +4,6 @@ import {
translate as t, translate as t,
} from '../../../helpers/api-integration/v4'; } from '../../../helpers/api-integration/v4';
describe('PUT /user', () => { describe('PUT /user', () => {
let user; let user;
@@ -53,7 +52,6 @@ describe('PUT /user', () => {
expect(user.tags.length).to.be.eql(userTags.length + 1); expect(user.tags.length).to.be.eql(userTags.length + 1);
}); });
it('profile.name cannot be an empty string or null', async () => { it('profile.name cannot be an empty string or null', async () => {
await expect(user.put('/user', { await expect(user.put('/user', {
'profile.name': ' ', // string should be trimmed 'profile.name': ' ', // string should be trimmed

View File

@@ -53,5 +53,11 @@ describe('POST /user/auth/verify-display-name', async () => {
displayName: 'this is a very long display name over 30 characters', displayName: 'this is a very long display name over 30 characters',
})).to.eventually.eql({ isUsable: false, issues: [t('displaynameIssueLength')] }); })).to.eventually.eql({ isUsable: false, issues: [t('displaynameIssueLength')] });
}); });
it('errors if display name contains a newline', async () => {
await expect(user.post(ENDPOINT, {
displayName: 'namecontainsnewline\n',
})).to.eventually.eql({ isUsable: false, issues: [t('displaynameIssueNewline')] });
});
}); });
}); });

View File

@@ -0,0 +1,25 @@
import getUtcOffset from '../../../website/common/script/fns/getUtcOffset';
describe('getUtcOffset', () => {
let user;
beforeEach(() => {
user = { preferences: {} };
});
it('returns 0 when user.timezoneOffset is not set', () => {
expect(getUtcOffset(user)).to.equal(0);
});
it('returns 0 when user.timezoneOffset is zero', () => {
user.preferences.timezoneOffset = 0;
expect(getUtcOffset(user)).to.equal(0);
});
it('returns the opposite of user.timezoneOffset', () => {
user.preferences.timezoneOffset = -10;
expect(getUtcOffset(user)).to.eql(10);
});
});

View File

@@ -0,0 +1,184 @@
import moment from 'moment';
import { startOfDay, daysSince } from '../../../website/common/script/cron';
function localMoment (timeString, utcOffset) {
return moment(timeString).utcOffset(utcOffset, true);
}
describe('cron utility functions', () => {
describe('startOfDay', () => {
it('is zero when no daystart configured', () => {
const options = { now: moment('2020-02-02 09:30:00Z'), timezoneOffset: 0 };
const result = startOfDay(options);
expect(result).to.be.sameMoment('2020-02-02 00:00:00Z');
});
it('is zero when negative daystart configured', () => {
const options = {
now: moment('2020-02-02 09:30:00Z'),
timezoneOffset: 0,
daystart: -5,
};
const result = startOfDay(options);
expect(result).to.be.sameMoment('2020-02-02 00:00:00Z');
});
it('is zero when daystart over 24 is configured', () => {
const options = {
now: moment('2020-02-02 09:30:00Z'),
timezoneOffset: 0,
daystart: 25,
};
const result = startOfDay(options);
expect(result).to.be.sameMoment('2020-02-02 00:00:00Z');
});
it('is equal to daystart o\'clock when daystart configured', () => {
const options = {
now: moment('2020-02-02 09:30:00Z'),
timezoneOffset: 0,
dayStart: 5,
};
const result = startOfDay(options);
expect(result).to.be.sameMoment('2020-02-02 05:00:00Z');
});
it('is previous day daystart o\'clock when daystart is after current time', () => {
const options = {
now: moment('2020-02-02 04:30:00Z'),
timezoneOffset: 0,
dayStart: 5,
};
const result = startOfDay(options);
expect(result).to.be.sameMoment('2020-02-01 05:00:00Z');
});
it('is daystart o\'clock when daystart is after current time due to timezone', () => {
const options = {
now: moment('2020-02-02 04:30:00Z'),
timezoneOffset: -120,
dayStart: 5,
};
const result = startOfDay(options);
expect(result).to.be.sameMoment('2020-02-02 05:00:00+02:00');
});
it('returns in default timezone if no timezone defined', () => {
const utcOffset = moment().utcOffset();
const now = localMoment('2020-02-02 04:30:00', utcOffset).utc();
const result = startOfDay({ now });
expect(result).to.be.sameMoment(localMoment('2020-02-02', utcOffset));
});
it('returns in default timezone if timezone lower than -12:00', () => {
const utcOffset = moment().utcOffset();
const options = {
now: localMoment('2020-02-02 17:30:00', utcOffset).utc(),
timezoneOffset: 721,
};
const result = startOfDay(options);
expect(result).to.be.sameMoment(localMoment('2020-02-02', utcOffset));
});
it('returns in default timezone if timezone higher than +14:00', () => {
const utcOffset = moment().utcOffset();
const options = {
now: localMoment('2020-02-02 07:32:25.376', utcOffset).utc(),
timezoneOffset: -841,
};
const result = startOfDay(options);
expect(result).to.be.sameMoment(localMoment('2020-02-02', utcOffset));
});
it('returns in overridden timezone if override present', () => {
const options = {
now: moment('2020-02-02 13:30:27Z'),
timezoneOffset: 0,
timezoneUtcOffsetOverride: -240,
};
const result = startOfDay(options);
expect(result).to.be.sameMoment('2020-02-02 00:00:00-04:00');
});
it('returns start of yesterday if timezone difference carries it over datelines', () => {
const offset = 300;
const options = {
now: moment('2020-02-02 04:30:00Z'),
timezoneOffset: offset,
};
const result = startOfDay(options);
expect(result).to.be.sameMoment(localMoment('2020-02-01', -offset));
});
});
describe('daysSince', () => {
it('correctly calculates days between two dates', () => {
const now = moment();
const dayBeforeYesterday = moment(now).subtract({ days: 2 });
expect(daysSince(dayBeforeYesterday, { now })).to.equal(2);
});
it('is one lower if current time is before dayStart', () => {
const oneWeekAgoAtOnePm = moment().hour(13).subtract({ days: 7 });
const thisMorningThreeAm = moment().hour(3);
const options = {
now: thisMorningThreeAm,
dayStart: 6,
};
const result = daysSince(oneWeekAgoAtOnePm, options);
expect(result).to.equal(6);
});
it('is one higher if reference time is before dayStart and current time after dayStart', () => {
const oneWeekAgoAtEightAm = moment().hour(8).subtract({ days: 7 });
const todayAtFivePm = moment().hour(17);
const options = {
now: todayAtFivePm,
dayStart: 11,
};
const result = daysSince(oneWeekAgoAtEightAm, options);
expect(result).to.equal(8);
});
// Variations in timezone configuration options are already covered by startOfDay tests.
it('uses now in user timezone as configured in options', () => {
const timezoneOffset = 120;
const options = {
now: moment('1989-11-09 02:53:00+01:00'),
timezoneOffset,
};
const result = daysSince(localMoment('1989-11-08', -timezoneOffset), options);
expect(result).to.equal(0);
});
});
});

View File

@@ -1,4 +1,3 @@
import { import {
generateUser, generateUser,
} from '../../helpers/common.helper'; } from '../../helpers/common.helper';
@@ -36,7 +35,6 @@ describe('getDebuffPotionItems', () => {
user.stats.buffs.snowball = true; user.stats.buffs.snowball = true;
user.stats.buffs.shinySeed = true; user.stats.buffs.shinySeed = true;
const result = getDebuffPotionItems(user); const result = getDebuffPotionItems(user);
expect(result).to.be.an('array').that.deep.include.members([ expect(result).to.be.an('array').that.deep.include.members([

View File

@@ -1,4 +1,3 @@
import { import {
generateUser, generateUser,
} from '../../helpers/common.helper'; } from '../../helpers/common.helper';
@@ -27,7 +26,6 @@ describe('setDebuffPotionItems', () => {
const firstSetResult = [...setDebuffPotionItems(user).pinnedItems]; const firstSetResult = [...setDebuffPotionItems(user).pinnedItems];
const secondSetResult = [...setDebuffPotionItems(user).pinnedItems]; const secondSetResult = [...setDebuffPotionItems(user).pinnedItems];
expect(firstSetResult).to.be.deep.equal(secondSetResult); expect(firstSetResult).to.be.deep.equal(secondSetResult);
}); });

View File

@@ -90,7 +90,6 @@ describe('shops', () => {
}, },
}); });
const shopWizardItems = shared.shops.getMarketGearCategories(userWithItems).find(x => x.identifier === 'wizard').items.filter(x => x.klass === 'wizard' && (x.owned === false || x.owned === undefined)); const shopWizardItems = shared.shops.getMarketGearCategories(userWithItems).find(x => x.identifier === 'wizard').items.filter(x => x.klass === 'wizard' && (x.owned === false || x.owned === undefined));
expect(shopWizardItems.length).to.eql(0); expect(shopWizardItems.length).to.eql(0);
}); });
@@ -122,7 +121,6 @@ describe('shops', () => {
}, },
}); });
const shopWizardItems = shared.shops.getMarketGearCategories(userWithItems).find(x => x.identifier === 'wizard').items.filter(x => x.klass === 'wizard' && (x.owned === false || x.owned === undefined)); const shopWizardItems = shared.shops.getMarketGearCategories(userWithItems).find(x => x.identifier === 'wizard').items.filter(x => x.klass === 'wizard' && (x.owned === false || x.owned === undefined));
expect(shopWizardItems.find(item => item.key === 'weapon_wizard_5').locked).to.eql(false); expect(shopWizardItems.find(item => item.key === 'weapon_wizard_5').locked).to.eql(false);
expect(shopWizardItems.find(item => item.key === 'weapon_wizard_6').locked).to.eql(true); expect(shopWizardItems.find(item => item.key === 'weapon_wizard_6').locked).to.eql(true);

View File

@@ -1,6 +1,7 @@
import moment from 'moment'; import moment from 'moment';
import taskDefaults from '../../../website/common/script/libs/taskDefaults'; import taskDefaults from '../../../website/common/script/libs/taskDefaults';
import getUtcOffset from '../../../website/common/script/fns/getUtcOffset';
import { generateUser } from '../../helpers/common.helper'; import { generateUser } from '../../helpers/common.helper';
describe('taskDefaults', () => { describe('taskDefaults', () => {
@@ -72,7 +73,7 @@ describe('taskDefaults', () => {
expect(task.startDate).to.eql( expect(task.startDate).to.eql(
moment() moment()
.zone(user.preferences.timezoneOffset, 'hour') .utcOffset(getUtcOffset(user))
.startOf('day') .startOf('day')
.subtract(1, 'day') .subtract(1, 'day')
.toDate(), .toDate(),

View File

@@ -75,7 +75,6 @@ describe('shared.ops.buyGem', () => {
expect(user.stats.gp).to.equal(goldPoints - planGemLimits.convRate * 2); expect(user.stats.gp).to.equal(goldPoints - planGemLimits.convRate * 2);
}); });
context('Failure conditions', () => { context('Failure conditions', () => {
it('returns an error when key is not provided', done => { it('returns an error when key is not provided', done => {
try { try {

View File

@@ -141,7 +141,6 @@ describe('shared.ops.buyQuest', () => {
} }
}); });
it('does not buy Gem-premium Quests', done => { it('does not buy Gem-premium Quests', done => {
user.stats.gp = 9999; user.stats.gp = 9999;
try { try {

View File

@@ -43,7 +43,6 @@ describe('shared.ops.purchase', () => {
} }
}); });
it('returns error when unknown type is provided', done => { it('returns error when unknown type is provided', done => {
try { try {
purchase(user, { params: { type: 'randomType', key: 'gem' } }); purchase(user, { params: { type: 'randomType', key: 'gem' } });
@@ -96,7 +95,6 @@ describe('shared.ops.purchase', () => {
} }
}); });
it('returns error when item is not found', done => { it('returns error when item is not found', done => {
const params = { key: 'notExisting', type: 'food' }; const params = { key: 'notExisting', type: 'food' };

View File

@@ -113,6 +113,30 @@ describe('shared.ops.feed', () => {
done(); done();
} }
}); });
it('does not allow bulk-feeding query amount above food owned', done => {
user.items.pets['Wolf-Base'] = 5;
user.items.food.Meat = 6;
try {
feed(user, { params: { pet: 'Wolf-Base', food: 'Meat' }, query: { amount: 8 } });
} catch (err) {
expect(err).to.be.an.instanceof(NotAuthorized);
expect(err.message).to.equal(i18n.t('notEnoughFood'));
done();
}
});
it('does not allow bulk-over-feeding pet', done => {
user.items.pets['Wolf-Base'] = 45;
user.items.food.Meat = 3;
try {
feed(user, { params: { pet: 'Wolf-Base', food: 'Meat' }, query: { amount: 2 } });
} catch (err) {
expect(err).to.be.an.instanceof(NotAuthorized);
expect(err.message).to.equal(i18n.t('tooMuchFood'));
done();
}
});
}); });
context('successful feeding', () => { context('successful feeding', () => {
@@ -188,6 +212,61 @@ describe('shared.ops.feed', () => {
expect(user.items.pets['Wolf-Base']).to.equal(7); expect(user.items.pets['Wolf-Base']).to.equal(7);
}); });
it('evolves the pet into a mount when feeding user.items.pets[pet] >= 50 preferred food (bulk)', () => {
user.items.pets['Wolf-Base'] = 5;
user.items.food.Meat = 10;
user.items.currentPet = 'Wolf-Base';
const pet = content.petInfo['Wolf-Base'];
const [data, message] = feed(user, { params: { pet: 'Wolf-Base', food: 'Meat' }, query: { amount: 9 } });
expect(data).to.eql(user.items.pets['Wolf-Base']);
expect(message).to.eql(i18n.t('messageEvolve', {
egg: pet.text(),
}));
expect(user.items.food.Meat).to.equal(1);
expect(user.items.pets['Wolf-Base']).to.equal(-1);
expect(user.items.mounts['Wolf-Base']).to.equal(true);
expect(user.items.currentPet).to.equal('');
});
it('evolves the pet into a mount when feeding user.items.pets[pet] >= 50 wrong food (bulk)', () => {
user.items.pets['Wolf-Base'] = 5;
user.items.food.Milk = 25;
user.items.currentPet = 'Wolf-Base';
const pet = content.petInfo['Wolf-Base'];
const [data, message] = feed(user, { params: { pet: 'Wolf-Base', food: 'Milk' }, query: { amount: 23 } });
expect(data).to.eql(user.items.pets['Wolf-Base']);
expect(message).to.eql(i18n.t('messageEvolve', {
egg: pet.text(),
}));
expect(user.items.food.Milk).to.equal(2);
expect(user.items.pets['Wolf-Base']).to.equal(-1);
expect(user.items.mounts['Wolf-Base']).to.equal(true);
expect(user.items.currentPet).to.equal('');
});
it('does not like the food (bulk low food) ', () => {
user.items.pets['Wolf-Base'] = 5;
user.items.food.Milk = 5;
const food = content.food.Milk;
const pet = content.petInfo['Wolf-Base'];
const [data, message] = feed(user, { params: { pet: 'Wolf-Base', food: 'Milk' }, query: { amount: 5 } });
expect(data).to.eql(user.items.pets['Wolf-Base']);
expect(message).to.eql(i18n.t('messageDontEnjoyFood', {
egg: pet.text(),
foodText: food.textThe(),
}));
expect(user.items.food.Milk).to.equal(0);
expect(user.items.pets['Wolf-Base']).to.equal(15);
});
it('awards All Your Base achievement', () => { it('awards All Your Base achievement', () => {
user.items.pets['Wolf-Spooky'] = 5; user.items.pets['Wolf-Spooky'] = 5;
user.items.food.Milk = 2; user.items.food.Milk = 2;

View File

@@ -29,7 +29,6 @@ describe('shared.ops.reset', () => {
tasksToRemove = [habit, todo, daily, reward]; tasksToRemove = [habit, todo, daily, reward];
}); });
it('resets a user', () => { it('resets a user', () => {
const [, message] = reset(user); const [, message] = reset(user);

View File

@@ -4,6 +4,7 @@ import {
import spells from '../../../website/common/script/content/spells'; import spells from '../../../website/common/script/content/spells';
import { import {
NotAuthorized, NotAuthorized,
BadRequest,
} from '../../../website/common/script/libs/errors'; } from '../../../website/common/script/libs/errors';
import i18n from '../../../website/common/script/i18n'; import i18n from '../../../website/common/script/i18n';
@@ -25,7 +26,7 @@ describe('shared.ops.spells', () => {
const spell = spells.healer.heal; const spell = spells.healer.heal;
try { try {
spell.cast(user); spell.cast(user, null, { language: 'en' });
} catch (err) { } catch (err) {
expect(err).to.be.an.instanceof(NotAuthorized); expect(err).to.be.an.instanceof(NotAuthorized);
expect(err.message).to.equal(i18n.t('messageHealthAlreadyMax')); expect(err.message).to.equal(i18n.t('messageHealthAlreadyMax'));
@@ -35,4 +36,22 @@ describe('shared.ops.spells', () => {
done(); done();
} }
}); });
it('Issue #12361: returns an error if chilling frost has already been cast', done => {
user.stats.class = 'wizard';
user.stats.lvl = 15;
user.stats.mp = 400;
user.stats.buffs.streaks = true;
const spell = spells.wizard.frost;
try {
spell.cast(user, null, { language: 'en' });
} catch (err) {
expect(err).to.be.an.instanceof(BadRequest);
expect(err.message).to.equal(i18n.t('spellAlreadyCast'));
expect(user.stats.mp).to.eql(400);
done();
}
});
}); });

View File

@@ -5,6 +5,8 @@ import 'moment-recur';
describe('shouldDo', () => { describe('shouldDo', () => {
let day; let let day; let
dailyTask; dailyTask;
// Options is a mapping of user.preferences, therefor `timezoneOffset` still holds old zone
// values instead of utcOffset values.
let options = {}; let options = {};
let nextDue = []; let nextDue = [];
@@ -80,17 +82,17 @@ describe('shouldDo', () => {
it('returns true if the user\'s current time is after start date and Custom Day Start', () => { it('returns true if the user\'s current time is after start date and Custom Day Start', () => {
options.dayStart = 4; options.dayStart = 4;
day = moment().zone(options.timezoneOffset).startOf('day').add(6, 'hours') day = moment().utcOffset(-options.timezoneOffset).startOf('day').add(6, 'hours')
.toDate(); .toDate();
dailyTask.startDate = moment().zone(options.timezoneOffset).startOf('day').toDate(); dailyTask.startDate = moment().utcOffset(-options.timezoneOffset).startOf('day').toDate();
expect(shouldDo(day, dailyTask, options)).to.equal(true); expect(shouldDo(day, dailyTask, options)).to.equal(true);
}); });
it('returns false if the user\'s current time is before Custom Day Start', () => { it('returns false if the user\'s current time is before Custom Day Start', () => {
options.dayStart = 8; options.dayStart = 8;
day = moment().zone(options.timezoneOffset).startOf('day').add(2, 'hours') day = moment().utcOffset(-options.timezoneOffset).startOf('day').add(2, 'hours')
.toDate(); .toDate();
dailyTask.startDate = moment().zone(options.timezoneOffset).startOf('day').toDate(); dailyTask.startDate = moment().utcOffset(-options.timezoneOffset).startOf('day').toDate();
expect(shouldDo(day, dailyTask, options)).to.equal(false); expect(shouldDo(day, dailyTask, options)).to.equal(false);
}); });
}); });
@@ -112,14 +114,14 @@ describe('shouldDo', () => {
it('returns true if the user\'s current time is after Custom Day Start', () => { it('returns true if the user\'s current time is after Custom Day Start', () => {
options.dayStart = 4; options.dayStart = 4;
day = moment().zone(options.timezoneOffset).startOf('day').add(6, 'hours') day = moment().utcOffset(-options.timezoneOffset).startOf('day').add(6, 'hours')
.toDate(); .toDate();
expect(shouldDo(day, dailyTask, options)).to.equal(true); expect(shouldDo(day, dailyTask, options)).to.equal(true);
}); });
it('returns false if the user\'s current time is before Custom Day Start', () => { it('returns false if the user\'s current time is before Custom Day Start', () => {
options.dayStart = 8; options.dayStart = 8;
day = moment().zone(options.timezoneOffset).startOf('day').add(2, 'hours') day = moment().utcOffset(-options.timezoneOffset).startOf('day').add(2, 'hours')
.toDate(); .toDate();
expect(shouldDo(day, dailyTask, options)).to.equal(false); expect(shouldDo(day, dailyTask, options)).to.equal(false);
}); });
@@ -377,7 +379,6 @@ describe('shouldDo', () => {
m: false, m: false,
}; };
[0, 1, 2, 3, 4, 5, 6].forEach(weekday => { [0, 1, 2, 3, 4, 5, 6].forEach(weekday => {
day = moment().add(1, 'weeks').day(weekday).toDate(); day = moment().add(1, 'weeks').day(weekday).toDate();

View File

@@ -1,9 +1,10 @@
import express from 'express'; import express from 'express';
import { v4 as uuid } from 'uuid'; import { v4 as uuid } from 'uuid';
import bodyParser from 'body-parser'; import bodyParser from 'body-parser';
import http from 'http';
const app = express(); const app = express();
const server = require('http').createServer(app); const server = http.createServer(app);
const PORT = process.env.TEST_WEBHOOK_APP_PORT || 3099; // eslint-disable-line no-process-env const PORT = process.env.TEST_WEBHOOK_APP_PORT || 3099; // eslint-disable-line no-process-env

View File

@@ -10,7 +10,6 @@ import {
export { translate } from './translate'; export { translate } from './translate';
export function generateUser (options = {}) { export function generateUser (options = {}) {
const user = new User(options).toObject(); const user = new User(options).toObject();

View File

@@ -8,8 +8,9 @@
//------------------------------ //------------------------------
global._ = require('lodash'); global._ = require('lodash');
global.chai = require('chai'); global.chai = require('chai');
chai.use(require('sinon-chai'));
chai.use(require('chai-as-promised')); chai.use(require('chai-as-promised'));
chai.use(require('chai-moment'));
chai.use(require('sinon-chai'));
global.expect = chai.expect; global.expect = chai.expect;
global.sinon = require('sinon'); global.sinon = require('sinon');

View File

@@ -1,6 +1,7 @@
import i18n from '../../website/common/script/i18n'; import i18n from '../../website/common/script/i18n';
import { translations } from '../../website/server/libs/i18n';
i18n.translations = require('../../website/server/libs/i18n').translations; i18n.translations = translations;
const STRING_ERROR_MSG = 'Error processing the string. Please see Help > Report a Bug.'; const STRING_ERROR_MSG = 'Error processing the string. Please see Help > Report a Bug.';
const STRING_DOES_NOT_EXIST_MSG = /^String '.*' not found.$/; const STRING_DOES_NOT_EXIST_MSG = /^String '.*' not found.$/;

View File

@@ -1,5 +1,4 @@
import { sync as glob } from 'glob';
const glob = require('glob').sync;
describe('Locales files', () => { describe('Locales files', () => {
it('do not contain duplicates of any keys', () => { it('do not contain duplicates of any keys', () => {

View File

@@ -1,6 +1,5 @@
import { sync as glob } from 'glob';
const glob = require('glob').sync; import { readFileSync as readFile } from 'fs';
const readFile = require('fs').readFileSync;
const IMPORT_REGEX = /(import|require).*common\/script/; const IMPORT_REGEX = /(import|require).*common\/script/;

View File

@@ -1,5 +1,6 @@
/* eslint-disable import/no-extraneous-dependencies */ /* eslint-disable import/no-extraneous-dependencies */
import { configure } from '@storybook/vue'; import { configure } from '@storybook/vue';
import './margin.css';
import '../../src/assets/scss/index.scss'; import '../../src/assets/scss/index.scss';
import '../../src/assets/css/sprites.css'; import '../../src/assets/css/sprites.css';
@@ -35,7 +36,7 @@ import BootstrapVue from 'bootstrap-vue';
import StoreModule from '@/libs/store'; import StoreModule from '@/libs/store';
// couldn't inject the languages easily, // couldn't inject the languages easily,
// so just a "$t()" string to show that this will be translated // so just a "$t()" string to show that this will be translated
Vue.prototype.$t = function translateString (...args) { Vue.prototype.$t = function translateString (...args) {
return `$t(${JSON.stringify(args)})`; return `$t(${JSON.stringify(args)})`;
}; };

View File

@@ -0,0 +1,13 @@
.background {
background: teal;
display: inline-block;
}
.content {
color: white;
background: grey;
}
.inline-block {
display: inline-block;
}

File diff suppressed because it is too large Load Diff

View File

@@ -16,50 +16,50 @@
"@storybook/addon-actions": "^5.3.19", "@storybook/addon-actions": "^5.3.19",
"@storybook/addon-knobs": "^5.3.19", "@storybook/addon-knobs": "^5.3.19",
"@storybook/addon-links": "^5.3.19", "@storybook/addon-links": "^5.3.19",
"@storybook/addon-notes": "^5.3.19", "@storybook/addon-notes": "^5.3.21",
"@storybook/vue": "^5.3.19", "@storybook/vue": "^5.3.19",
"@vue/cli-plugin-babel": "^4.4.4", "@vue/cli-plugin-babel": "^4.5.4",
"@vue/cli-plugin-eslint": "^4.4.4", "@vue/cli-plugin-eslint": "^4.5.4",
"@vue/cli-plugin-router": "^4.4.4", "@vue/cli-plugin-router": "^4.5.4",
"@vue/cli-plugin-unit-mocha": "^4.4.4", "@vue/cli-plugin-unit-mocha": "^4.5.4",
"@vue/cli-service": "^4.4.4", "@vue/cli-service": "^4.5.4",
"@vue/test-utils": "1.0.0-beta.29", "@vue/test-utils": "1.0.0-beta.29",
"amplitude-js": "^6.2.0", "amplitude-js": "^7.1.1",
"axios": "^0.19.2", "axios": "^0.19.2",
"axios-progress-bar": "^1.2.0", "axios-progress-bar": "^1.2.0",
"babel-eslint": "^10.1.0", "babel-eslint": "^10.1.0",
"bootstrap": "^4.5.0", "bootstrap": "^4.5.2",
"bootstrap-vue": "^2.15.0", "bootstrap-vue": "^2.16.0",
"chai": "^4.1.2", "chai": "^4.1.2",
"core-js": "^3.6.5", "core-js": "^3.6.5",
"eslint": "^6.8.0", "eslint": "^6.8.0",
"eslint-config-habitrpg": "^6.2.0", "eslint-config-habitrpg": "^6.2.0",
"eslint-plugin-mocha": "^5.3.0", "eslint-plugin-mocha": "^5.3.0",
"eslint-plugin-vue": "^6.2.2", "eslint-plugin-vue": "^6.2.2",
"habitica-markdown": "^2.0.2", "habitica-markdown": "^3.0.0",
"hellojs": "^1.18.4", "hellojs": "^1.18.4",
"inspectpack": "^4.5.2", "inspectpack": "^4.5.2",
"intro.js": "^2.9.3", "intro.js": "^2.9.3",
"jquery": "^3.5.1", "jquery": "^3.5.1",
"lodash": "^4.17.15", "lodash": "^4.17.20",
"moment": "^2.27.0", "moment": "^2.27.0",
"nconf": "^0.10.0", "nconf": "^0.10.0",
"sass": "^1.26.8", "sass": "^1.26.10",
"sass-loader": "^8.0.2", "sass-loader": "^8.0.2",
"smartbanner.js": "^1.16.0", "smartbanner.js": "^1.16.0",
"svg-inline-loader": "^0.8.2", "svg-inline-loader": "^0.8.2",
"svg-url-loader": "^6.0.0", "svg-url-loader": "^6.0.0",
"svgo": "^1.3.2", "svgo": "^1.3.2",
"svgo-loader": "^2.2.1", "svgo-loader": "^2.2.1",
"uuid": "^8.1.0", "uuid": "^8.3.0",
"validator": "^13.1.1", "validator": "^13.1.1",
"vue": "^2.6.11", "vue": "^2.6.12",
"vue-cli-plugin-storybook": "^0.6.1", "vue-cli-plugin-storybook": "^0.6.1",
"vue-mugen-scroll": "^0.2.6", "vue-mugen-scroll": "^0.2.6",
"vue-router": "^3.3.4", "vue-router": "^3.4.3",
"vue-template-compiler": "^2.6.11", "vue-template-compiler": "^2.6.12",
"vuedraggable": "^2.23.2", "vuedraggable": "^2.24.1",
"vuejs-datepicker": "git://github.com/habitrpg/vuejs-datepicker.git#5d237615463a84a23dd6f3f77c6ab577d68593ec", "vuejs-datepicker": "git://github.com/habitrpg/vuejs-datepicker.git#153d339e4dbebb73733658aeda1d5b7fcc55b0a0",
"webpack": "^4.43.0" "webpack": "^4.44.1"
} }
} }

View File

@@ -119,7 +119,6 @@
#melior { #melior {
margin: 0 auto; margin: 0 auto;
width: 70.9px; width: 70.9px;
margin-bottom: 1em;
} }
.row { .row {
@@ -218,11 +217,6 @@
opacity: 0.48; opacity: 0.48;
} }
/* @TODO: The modal-open class is not being removed. Let's try this for now */
.modal {
overflow-y: scroll !important;
}
.modal-backdrop { .modal-backdrop {
opacity: .9 !important; opacity: .9 !important;
background-color: $purple-100 !important; background-color: $purple-100 !important;
@@ -297,7 +291,7 @@ export default {
}; };
}, },
computed: { computed: {
...mapState(['isUserLoggedIn', 'browserTimezoneOffset', 'isUserLoaded', 'notificationsRemoved']), ...mapState(['isUserLoggedIn', 'browserTimezoneUtcOffset', 'isUserLoaded', 'notificationsRemoved']),
...mapState({ user: 'user.data' }), ...mapState({ user: 'user.data' }),
isStaticPage () { isStaticPage () {
return this.$route.meta.requiresLogin === false; return this.$route.meta.requiresLogin === false;
@@ -369,7 +363,6 @@ export default {
const isApiCall = url.indexOf('api/v4') !== -1; const isApiCall = url.indexOf('api/v4') !== -1;
const userV = response.data && response.data.userV; const userV = response.data && response.data.userV;
const isCron = url.indexOf('/api/v4/cron') === 0 && method === 'post';
if (this.isUserLoaded && isApiCall && userV) { if (this.isUserLoaded && isApiCall && userV) {
const oldUserV = this.user._v; const oldUserV = this.user._v;
@@ -381,9 +374,14 @@ export default {
// exclude chat seen requests because with real time chat they would be too many // exclude chat seen requests because with real time chat they would be too many
const isChatSeen = url.indexOf('/chat/seen') !== -1 && method === 'post'; const isChatSeen = url.indexOf('/chat/seen') !== -1 && method === 'post';
// exclude POST /api/v4/cron because the user is synced automatically after cron runs // exclude POST /api/v4/cron because the user is synced automatically after cron runs
const isCron = url.indexOf('/api/v4/cron') === 0 && method === 'post';
// exclude skills casting as they already return the synced user
const isCast = url.indexOf('/api/v4/user/class/cast') !== -1 && method === 'post';
// Something has changed on the user object that was not tracked here, sync the user // Something has changed on the user object that was not tracked here, sync the user
if (userV - oldUserV > 1 && !isCron && !isChatSeen && !isUserSync && !isTasksSync) { if (
userV - oldUserV > 1 && !isCron && !isChatSeen && !isUserSync && !isTasksSync && !isCast
) {
Promise.all([ Promise.all([
this.$store.dispatch('user:fetch', { forceLoad: true }), this.$store.dispatch('user:fetch', { forceLoad: true }),
this.$store.dispatch('tasks:fetchUserTasks', { forceLoad: true }), this.$store.dispatch('tasks:fetchUserTasks', { forceLoad: true }),
@@ -489,9 +487,10 @@ export default {
this.hideLoadingScreen(); this.hideLoadingScreen();
// Adjust the timezone offset // Adjust the timezone offset
if (this.user.preferences.timezoneOffset !== this.browserTimezoneOffset) { const browserTimezoneOffset = -this.browserTimezoneUtcOffset;
if (this.user.preferences.timezoneOffset !== browserTimezoneOffset) {
this.$store.dispatch('user:set', { this.$store.dispatch('user:set', {
'preferences.timezoneOffset': this.browserTimezoneOffset, 'preferences.timezoneOffset': browserTimezoneOffset,
}); });
} }
@@ -513,13 +512,9 @@ export default {
} else { } else {
this.hideLoadingScreen(); this.hideLoadingScreen();
} }
this.initializeModalStack();
}, },
beforeDestroy () { beforeDestroy () {
this.$root.$off('playSound'); this.$root.$off('playSound');
this.$root.$off('bv::modal::hidden');
this.$root.$off('bv::show::modal');
this.$root.$off('buyModal::showItem'); this.$root.$off('buyModal::showItem');
this.$root.$off('selectMembersModal::showItem'); this.$root.$off('selectMembersModal::showItem');
}, },
@@ -549,112 +544,6 @@ export default {
this.$store.dispatch('auth:logout', { redirectToLogin: true }); this.$store.dispatch('auth:logout', { redirectToLogin: true });
return true; return true;
}, },
initializeModalStack () {
// Manage modals
this.$root.$on('bv::show::modal', (modalId, data = {}) => {
if (data.fromRoot) return;
const { modalStack } = this.$store.state;
this.trackGemPurchase(modalId, data);
// Add new modal to the stack
const prev = modalStack[modalStack.length - 1];
const prevId = prev ? prev.modalId : undefined;
modalStack.push({ modalId, prev: prevId });
});
this.$root.$on('bv::modal::hidden', bvEvent => {
let modalId = bvEvent.target && bvEvent.target.id;
// sometimes the target isn't passed to the hidden event, fallback is the vueTarget
if (!modalId) {
modalId = bvEvent.vueTarget && bvEvent.vueTarget.id;
}
if (!modalId) {
return;
}
const { modalStack } = this.$store.state;
const modalOnTop = modalStack[modalStack.length - 1];
// Check for invalid modal. Event systems can send multiples
if (!this.validStack(modalStack)) return;
// If we are moving forward
if (modalOnTop && modalOnTop.prev === modalId) return;
// Remove modal from stack
this.$store.state.modalStack.pop();
// Get previous modal
const modalBefore = modalOnTop ? modalOnTop.prev : undefined;
if (modalBefore) this.$root.$emit('bv::show::modal', modalBefore, { fromRoot: true });
});
// Dismiss modal aggressively. Pass a modal ID to remove a modal instance from the stack
// (both the stack entry itself and its "prev" reference) so we don't reopen it
this.$root.$on('habitica::dismiss-modal', oldModal => {
if (!oldModal) return;
this.$root.$emit('bv::hide::modal', oldModal);
let removeIndex = this.$store.state.modalStack
.map(modal => modal.modalId)
.indexOf(oldModal);
if (removeIndex >= 0) {
this.$store.state.modalStack.splice(removeIndex, 1);
}
removeIndex = this.$store.state.modalStack
.map(modal => modal.prev)
.indexOf(oldModal);
if (removeIndex >= 0) {
delete this.$store.state.modalStack[removeIndex].prev;
}
});
},
validStack (modalStack) {
const modalsThatCanShowTwice = ['profile'];
const modalCount = {};
const prevAndCurrent = 2;
for (const current of modalStack) {
if (!modalCount[current.modalId]) modalCount[current.modalId] = 0;
modalCount[current.modalId] += 1;
if (
modalCount[current.modalId] > prevAndCurrent
&& modalsThatCanShowTwice.indexOf(current.modalId) === -1
) {
this.$store.state.modalStack = [];
return false;
}
if (!current.prev) continue; // eslint-disable-line
if (!modalCount[current.prev]) modalCount[current.prev] = 0;
modalCount[current.prev] += 1;
if (
modalCount[current.prev] > prevAndCurrent
&& modalsThatCanShowTwice.indexOf(current.prev) === -1
) {
this.$store.state.modalStack = [];
return false;
}
}
return true;
},
trackGemPurchase (modalId, data) {
// Track opening of gems modal unless it's been already tracked
// For example the gems button in the menu already tracks the event by itself
if (modalId === 'buy-gems' && data.alreadyTracked !== true) {
Analytics.track({
hitType: 'event',
eventCategory: 'button',
eventAction: 'click',
eventLabel: 'Gems > Wallet',
});
}
},
itemSelected (item) { itemSelected (item) {
this.selectedItemToBuy = item; this.selectedItemToBuy = item;
}, },

View File

@@ -4,16 +4,27 @@
height: 219px; height: 219px;
} }
.Pet_HatchingPotion_Dessert { .quest_windup {
background: url("~@/assets/images/animated/Pet_HatchingPotion_Dessert.gif") no-repeat; background: url("~@/assets/images/animated/quest_windup.gif") no-repeat;
width: 219px;
height: 219px;
}
.Pet_HatchingPotion_Dessert, .Pet_HatchingPotion_Veggie, .Pet_HatchingPotion_Windup {
width: 68px; width: 68px;
height: 68px; height: 68px;
} }
.Pet_HatchingPotion_Dessert {
background: url("~@/assets/images/animated/Pet_HatchingPotion_Dessert.gif") no-repeat;
}
.Pet_HatchingPotion_Veggie { .Pet_HatchingPotion_Veggie {
background: url("~@/assets/images/animated/Pet_HatchingPotion_Veggie.gif") no-repeat; background: url("~@/assets/images/animated/Pet_HatchingPotion_Veggie.gif") no-repeat;
width: 68px; }
height: 68px;
.Pet_HatchingPotion_Windup {
background: url("~@/assets/images/animated/Pet_HatchingPotion_Windup.gif") no-repeat;
} }
.Gems { .Gems {

View File

@@ -1,72 +1,78 @@
.promo_armoire_backgrounds_202006 { .promo_armoire_backgrounds_202007 {
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png'); background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: 0px -573px; background-position: -376px 0px;
width: 423px; width: 423px;
height: 147px; height: 147px;
} }
.promo_mystery_202006 { .promo_armoire_backgrounds_202008 {
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png'); background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -445px -211px; background-position: -376px -148px;
width: 423px;
height: 147px;
}
.promo_armoire_backgrounds_202009 {
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: 0px -383px;
width: 423px;
height: 147px;
}
.promo_golden_achievements {
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -295px -531px;
width: 246px;
height: 112px;
}
.promo_mystery_202008 {
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: 0px -531px;
width: 294px;
height: 156px;
}
.promo_mystery_202009 {
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -424px -383px;
width: 282px; width: 282px;
height: 147px; height: 147px;
} }
.promo_sand_sculpture_potions {
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -424px -573px;
width: 423px;
height: 147px;
}
.promo_splashy_skins {
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: 0px -386px;
width: 375px;
height: 186px;
}
.customize-option.promo_splashy_skins {
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -25px -401px;
width: 60px;
height: 60px;
}
.promo_summer_splash_2019 {
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: 0px -199px;
width: 408px;
height: 186px;
}
.promo_summer_splash_2020 {
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: 0px 0px;
width: 444px;
height: 198px;
}
.promo_take_this { .promo_take_this {
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png'); background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -927px -389px; background-position: -800px -537px;
width: 96px; width: 96px;
height: 69px; height: 69px;
} }
.promo_time_travelers {
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: 0px 0px;
width: 375px;
height: 186px;
}
.scene_CernyPie {
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: 0px -688px;
width: 141px;
height: 167px;
}
.scene_achievement { .scene_achievement {
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png'); background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -445px 0px; background-position: -800px 0px;
width: 339px; width: 210px;
height: 210px; height: 210px;
} }
.scene_hiking { .scene_public_space {
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png'); background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -785px 0px; background-position: 0px -187px;
width: 258px; width: 345px;
height: 258px; height: 195px;
} }
.scene_nakonana { .scene_reading {
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png'); background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -785px -389px; background-position: -800px -392px;
width: 141px; width: 171px;
height: 169px; height: 144px;
} }
.scene_strength { .scene_rewards {
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png'); background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -785px -259px; background-position: -800px -211px;
width: 192px; width: 207px;
height: 129px; height: 180px;
} }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

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